基本概念
Promise 对象内部会维护自身所处的状态,在被调用resolve()
或者reject()
之前,是 pending 状态。被 resolve 之后是 resolved 状态,若有异常抛出则是 rejected 状态。后两种状态统称 sattled,一旦进入 sattled 状态就不会再改变了(尝试进行改变会被忽略)。
一个 Promise 对象可以使用then
以及catch
方法挂载处理函数(handler),当状态变化时,对应的处理函数被调用。状态转移时可以传递一个转移参数被 handler 接收。
基本 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
})
p
实际上是一个已经处于 rejected 状态的 Promise,并且转移参数是一个 TypeError。- 之后,把①立即放入宏任务的队尾。
- 调用
then
方法,但是没有错误处理函数,这导致错误被再次抛出,并传给下一个catch。 - 调用
catch
方法处理异常,但是没有返回值,因此将返回一个终态是 resolved 状态的 Promise,参数是 undefined。 - 调用
catch
方法处理异常。
但是执行顺序呢?让我们做一回人肉解释器:
- 执行同步代码,此时注释中的②④⑥被推入微任务队列中,而③⑤在等待 pending 状态的 Promise(在等待②④的执行),还不处于微任务队列。
- 同步代码执行完了,执行微任务。②执行,由于没有 error handler,相当于内部再次抛出异常,导致此次
then
调用返回的 Promise 被决议为 rejected 状态,其后续的处理函数③被推入微任务;同理执行④后,⑤被推入队列。此时微任务队列:⑥③⑤ - 执行完微任务,到下一个宏任务。
因此输出为:
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