基本概念

Promise 对象内部会维护自身所处的状态,在被调用resolve()或者reject()之前,是 pending 状态。被 resolve 之后是 resolved 状态,若有异常抛出则是 rejected 状态。后两种状态统称 sattled,一旦进入 sattled 状态就不会再改变了(尝试进行改变会被忽略)。

一个 Promise 对象可以使用then以及catch方法挂载处理函数(handler),当状态变化时,对应的处理函数被调用。状态转移时可以传递一个转移参数被 handler 接收。

image.png

基本 API

静态方法

Promise.resolve():这个方法返回一个新的 Promise 对象,对象的状态是 resolved,并且将参数作为转移参数。

Promise.reject():返回一个已经 rejected 的新 Promise 对象,将参数作为转移参数。

Promise.all:and gate

Promise.race:or gate

实例方法

Promise.prototype.then():这个方法接收两个参数:1)转移到 resolved 的处理函数;2)转移到 rejected 的处理函数。如果对应位置传入的不是函数,则会使用默认处理函数

默认函数的行为:直接将接受到的转移参数返回(或异常直接抛出)。因此pr.then().catch()这种链式调用实际上是让第二步then中的默认错误处理函数再次抛出了异常,再被第三次的catch捕获。

then一定会返回一个新的 Promise 对象,但是对应传入then中的handler返回值,then的返回值本身也有所不同:

handler 返回值 then 返回值
新的 Promise 对象 该对象本身
返回一个值 一个终态是 resolved 状态的 Promise,转移参数是该值
什么都不返回 一个终态是 resolved 状态的 Promise,转移参数是 undefined
中途抛出异常 一个终态是 rejected 状态的 Promise,转移参数是该异常

上面的说法有些奇怪,什么叫终态是xx状态?关键在于区分then()这个函数本身的执行,以及传入的 handler 函数执行。then函数在 Promise 被决议后立即执行,负责把传入的 handler 的执行推到微任务队列中。但是 then 返回的 Promise 取决于 handler 的返回值,因此在 handler 执行完毕之前,then()的返回值是 pending 状态,直到 handler 执行完毕才转换到 sattled 态。对于后续链式调用then绑定的 handler,也只有在前一个 handler 执行完之后,才继续推入微任务。

Promise.prototype.catch(handler):这个方法实际上是then(undefined, handler)的语法糖,catch方法内部使用这一then调用来处理错误,同时返回这一then调用的返回值。因此catch的返回值,与上述then返回值规则相同,取决于传入的错误处理函数的返回值。

Quiz

一道结合了事件循环和 Promise 的测试题。

let p = Promise.reject(new TypeError());

setTimeout(function() {
  console.log('macro task'); // 1
}, 0);

p.then(v => {
  console.log(v); // 2
}).catch(err => {
  console.log('oops'); // 3
  console.log(err);
})

p.catch(err => {
  console.log('noooop'); // 4
  console.log(err);
  throw new Error();
}).catch(err => {
  console.log('haha'); // 5
})

p.catch(err2 => {
  console.log('err2'); // 6
})
  1. p实际上是一个已经处于 rejected 状态的 Promise,并且转移参数是一个 TypeError。
  2. 之后,把①立即放入宏任务的队尾。
  3. 调用then方法,但是没有错误处理函数,这导致错误被再次抛出,并传给下一个catch。
  4. 调用catch方法处理异常,但是没有返回值,因此将返回一个终态是 resolved 状态的 Promise,参数是 undefined。
  5. 调用catch方法处理异常。

但是执行顺序呢?让我们做一回人肉解释器:

  1. 执行同步代码,此时注释中的②④⑥被推入微任务队列中,而③⑤在等待 pending 状态的 Promise(在等待②④的执行),还不处于微任务队列。
  2. 同步代码执行完了,执行微任务。②执行,由于没有 error handler,相当于内部再次抛出异常,导致此次then调用返回的 Promise 被决议为 rejected 状态,其后续的处理函数③被推入微任务;同理执行④后,⑤被推入队列。此时微任务队列:⑥③⑤
  3. 执行完微任务,到下一个宏任务。

因此输出为:

noooop
(index):49 TypeError
    at (index):34
(index):56 err2
(index):43 oops
(index):44 TypeError
    at (index):34
(index):52 haha
(index):37 macro task