ES6 (11) Promise与异步编程

为什么要异步编程

我们在写前端代码时,经常会对DOM做事件处理操作,比如点击、激活焦点、失去焦点等;再比如我们用ajax请求数据,使用回调函数获取返回值。这些都属于异步编程。

也许你已经大概知道JavaScript引擎单线程的概念,那么这种单线程模式和异步编程有什么关系呢?

JavaScript引擎中,只有一个主线程,当执行JavaScript代码块时,不允许其他代码块执行,而事件机制和回调机制的代码块会被添加到任务队列中,当符合某个触发回调或者事件的时候,就会执行该事件或者回调函数。

事件模型:?浏览器初次渲染DOM的时候,我们会给一些DOM绑定事件函数,只有当触发了这些DOM事件函数,才会执行他们。

const btn = document.querySelector('.button')
btn.onclick = function(event) {
  console.log(event)
}

回调模式:?nodejs中可能非常常见这种回调模式,但是对于前端来说,ajax的回调是最熟悉不过了。ajax回调有多个状态,当响应成功和失败都有不同的回调函数。

$.post('/router', function(data) {
  console.log(data)
})

回调也可能带来一个问题,那就是回调地狱.

Promise

事件函数没有问题,我们用的很爽,问题出在回调函数,尤其是指地狱回调,Promise的出现正是为了避免地狱回调带来的困扰。

推荐你看JavaScript MDN Promise教程,然后再结合本文看,你就能学会使用Promise了。

Promise是什么

Promise的中文意思是承诺,也就是说,JavaScript对你许下一个承诺,会在未来某个时刻兑现承诺。

Promise生命周期

react有生命周期,vue也有生命周期,就连Promise也有生命周期,现在生命周期咋这么流行了。

Promise的生命周期:进行中(pending),已经完成(fulfilled),拒绝(rejected)

Promise被称作异步结果的占位符,它不能直接返回异步函数的执行结果,需要使用then(),当获取异常回调的时候,使用catch()。

这次我们使用axios插件的代码做例子。axios是前端比较热门的http请求插件之一。

1、创建axios实例instance。

import axios from 'axios'
export const instance = axios.create()

2、使用axios实例 + Promise获取返回值。

const promise = instance.get('url')    
promise.then(result => console.log(result)).catch(err => console.log(err))

使用Promise构建函数创建新的Promise

Promise构造函数只有一个参数,该参数是一个函数,被称作执行器,执行器有2个参数,分别是resolve()和reject(),一个表示成功的回调,一个表示失败的回调。

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(5), 0)
}).then(v => console.log(v)) // 5

记住,Promise实例只能通过resolve或者reject函数来返回,并且使用then()或者catch()获取,不能在new Promise里面直接return,这样是获取不到Promise返回值的。

1、我们也可以使用Promise直接resolve(value)。

Promise.resolve(5).then(v => console.log(v)) // 5

2、也可以使用reject(value)

Promise.reject(5).catch(v => console.log(v)) // 5

3、执行器错误通过catch捕捉。

new Promise(function(resolve, reject) {
  if(true) {
    throw new Error('error!!')
  }
}).catch(v => console.log(v.message)) // error!!

 

Promise链式调用

这个例子中,使用了3个then,第一个then返回 s * s,第二个then捕获到上一个then的返回值,最后一个then直接输出end。这就叫链式调用,很好理解的。我只使用了then(),实际开发中,你还应该加上catch()。

new Promise(function(resolve, reject) {
  try {
    resolve(5)
  } catch (error) {
    reject('It was my wrong!!!')
  }
}).then(s => s * s).then(s2 => console.log(s2)).then(() => console.log('end'))
// 25  "end"

Promise的用法

定义方法:

function runAsync1(){
  var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
      console.log('异步任务1执行完成');
      resolve('随便什么数据1');
    }, 1000);
  });
  return p;            
}
function runAsync2(){
  var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
      console.log('异步任务2执行完成');
      resolve('随便什么数据2');
    }, 2000);
  });
  return p;            
}
function runAsync3(){
  var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
      console.log('异步任务3执行完成');
      resolve('随便什么数据3');
    }, 2000);
  });
  return p;            
}

调用:

runAsync1()
.then(function(data){
  console.log(data);
  return runAsync2();
})
.then(function(data){
  console.log(data);
  return runAsync3(); 
})
.then(function(data){
  console.log(data);
});

// 异步任务1执行完成
// 随便什么数据1
// 异步任务2执行完成
// 随便什么数据2
// 异步任务3执行完成
// 随便什么数据3

Promise的其他方法

在Promise的构造函数中,除了reject()和resolve()之外,还有2个方法,Promise.all()、Promise.race()。

Promise.all():

前面我们的例子都是只有一个Promise,现在我们使用all()方法包装多个Promise实例。

语法很简单:参数只有一个,可迭代对象,可以是数组,或者Symbol类型等。

Promise.all(iterable).then().catch()

示例:传入3个Promise实例

Promise.all([
  new Promise(function(resolve, reject) {
    resolve(1)
  }),
  new Promise(function(resolve, reject) {
    resolve(2)
  }),
  new Promise(function(resolve, reject) {
    resolve(3)
  })
]).then(arr => {
  console.log(arr) // [1, 2, 3]
})

**Promise.race():**语法和all()一样,但是返回值有所不同,race根据传入的多个Promise实例,只要有一个实例resolve或者reject,就只返回该结果,其他实例不再执行。

还是使用上面的例子,只是我给每个resolve加了一个定时器,最终结果返回的是3,因为第三个Promise最快执行。

Promise.race([
  new Promise(function(resolve, reject) {
    setTimeout(() => resolve(1), 1000)
  }),
  new Promise(function(resolve, reject) {
    setTimeout(() => resolve(2), 100)
  }),
  new Promise(function(resolve, reject) {
    setTimeout(() => resolve(3), 10)
  })
]).then(value => {
  console.log(value) // 3
})

Promise派生

派生的意思是定义一个新的Promise对象,继承Promise方法和属性。

class MyPromise extends Promise {    
  //重新封装then()
  success(resolve, reject) {
    return this.then(resolve, reject)
  }
  //重新封装catch()
  failer(reject) {
    return this.catch(reject)
  }
}

接着我们来使用一下这个派生类。 ?

new MyPromise(function(resolve, reject) {
  resolve(10)
}).success(v => console.log(v)) // 10

如果只是派生出来和then、catch一样的方法,我想,你不会干这么无聊的事情。

Promise和异步的联系

Promise本身不是异步的,只有他的then()或者catch()方法才是异步,也可以说Promise的返回值是异步的。通常Promise被使用在node,或者是前端的ajax请求、前端DOM渲染顺序等地方。

###比Promise更好的异步方案async(ES2017/ES8标准实现)。

async function a() {
  await function() {}
}

总结

Promise是什么、怎么用、怎么获取返回值?是本章的中心内容,多看几遍,你会发现使用Promise是非常简单的事情。

本文链接地址: ES6 (11) Promise与异步编程

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注