ES6 异步解决方案 Promise

背景

JS 经常会遇到一些异步任务,所谓异步任务,就是需要经过一段时间 或 当某个时机到达后才能得到结果的任务

例如:

  • 使用 ajax 请求服务器,当服务器完成响应后拿到响应结果
  • 监听按钮是否被点击,当按钮被点击后拿到某个文本框的值
  • 使用 setTimeout 等待一段时间,当时间到达后做某些事情

面对这样的场景,JS 没有一种标准的模式来进行处理,我们处理这些问题的方式是杂乱的,这就导致了不同的人书写的异步任务代码使用方式不一致。

另外,过去常常使用回调的方式来处理异步场景,这种方式又容易产生回调地狱(callback hell)

ES6 总结了各种异步场景,并提取出一种通用的异步模型

ES6 的异步处理模型

ES6 将异步场景分为两个阶段三种状态

两个阶段:unsettled(未决) 和 settled(已决) 三种状态:pending(挂起)、resolved(完成)、rejected(失败)

他们的关系图如下:

当任务处于未决阶段时,它一定是 pending 挂起状态,表示任务从开始到拿到结果之间的过程。比如:网络完成了各种配置,也发送了请求,但是请求结果还没有拿到。

当任务处于 已决阶段时,它只能是 resolvedrejected两种状态的一种,表示任务有了一个结果。比如:从服务器拿到了数据(resolved)、网络不好没有拿到数据(rejected)

任务开始时,始终是未决阶段,那任务如何才能走向已决阶段呢?

ES6 认为,任务在未决阶段的时候,有能力将其推向已决。比如,当从服务器拿到数据后,我们就从未决阶段推向已决的 resolved 状态,如果网络不好,导致出错了,我们就从未决阶段推向已决的 rejected 状态

我们把从未决推向已决的 resolved 状态的过程,叫做 resolve从未决推向已决的 rejected 状态的过程,叫做 reject,如下图所示

这种状态和阶段的变化是不可逆的,也就是说,一旦推向了已决,就无法重新改变状态

任务从未决到已决时,可能附带一些数据,比如:跑步完成后的用时、网络请求后从服务器拿到的数据

任务已决后(有了结果),可能需要进一步做后续处理,如果任务成功了(resolved),有后续处理,如果任务失败了(rejected),仍然可能有后续处理

我们把针对 resolved 的后续处理,称之为 thenable,针对 rejected 的后续处理,称之为 catchable

Promise 的基本使用

ES 官方制定了一个全新的 API 来适配上面提到的异步模型,这个 API 即 Promise

Promise 是一个构造函数,通过new Promise()可以创建一个任务对象,构造函数的参数是一个函数,用于处理未决阶段的事务,该函数的执行是立即同步执行的。在函数中,可以通过两个参数自主的在合适的时候将任务推向已决阶段

var pro = new Promise((resolve, reject) => {
 //未决阶段的代码,这些代码将立即执行
 //...
 //在合适的时候,将任务推向已决
 //resolve(数据):将任务推向resovled状态,并附加一些数据
 //reject(数据):将任务推向rejected状态,并附加一些数据
})

注意

  1. 任务一旦进入已决后,所有企图改变任务状态的代码都将失效
  2. 以下代码可以让任务到达 rejected 状态
    1. 调用 reject
    2. 代码执行报错
    3. 抛出错误

拿到 Promise 对象后,可以通过 then 方法指定后续处理

pro.then(thenable, catchable)
//或
pro.then(thenable)
pro.catch(catchable)

无论是 thenable 还是 catchable,均是下面格式的函数

function (data){
    //data为状态数据
}

注意:后续处理函数一定是异步函数,并且放到微队列中