从零开始写一个 Promise 库
Promise 已经是 JavaScript 中异步处理的基石,回调的场景将会越来越少,而且现在可以直接在 Node.js 使用 async/await。async/await 基于 Promise,因此需要了解 Promise 来掌握 async/await。这篇文章,将介绍如何编写一个 Promise 库,并演示如何使用 async/await。
Promise 是什么?
在 ES6 规范中,Promise 是一个类,它的构造函数接受一个 executor 函数。Promise 类的实例有一个 then() 方法。根据规范,Promise 还有其他的一些属性,但在这里可以暂时忽略,因为我们要实现的是一个精简版的库。下面是一个 MyPromise 类的脚手架:
class MyPromise {
// `executor` 函数接受两个参数,`resolve()` 和 `reject()`
// 负责在异步操作成功(resolved)或者失败(rejected)的时候调用 `resolve()` 或者 `reject()`
constructor(executor) {}
// 当 promise 的状态是 fulfilled(完成)时调用 `onFulfilled` 方法,
// 当 promise 的状态是 rejected(失败)时调用 `onRejected` 方法
// 到目前为止,可以认为 'fulfilled' 和 'resolved' 是一样的
then(onFulfilled, onRejected) {}
}executor 函数需要两个参数,resolve() 和 reject()。promise 是一个状态机,包含三个状态:
pending:初始状态,既不是成功,也不是失败状态
fulfilled:意味着操作成功完成,返回结果值
rejected:意味着操作失败,返回错误信息
这样很容易就能实现 MyPromise 构造函数的初始版本:
then() 函数的实现更简单,它接受两个参数,onFulfilled() 和 onRejected()。then() 函数必须确保 promise 在 fulfilled 时调用 onFulfilled(),在 rejected 时调用 onRejected()。如果 promise 已经 resolved 或 rejected,then() 函数会立即调用 onFulfilled() 或 onRejected()。如果 promise 仍处于 pending 状态,就将函数推入 $chained 数组,因此后续 resolve() 和 reject() 函数仍然可以调用它们。
*除此之外:ES6 规范表示,如果在已经 resolved 或 rejected 的 promise 调用 .then(), 那么 onFulfilled() 或 onRejected() 将在下一个时序被调用。由于本文代码只是一个教学示例而不是规范的精确实现,因此实现会忽略这些细节。
Promise 调用链
上面的例子特意忽略了 promise 中最复杂也是最有用的部分:链式调用。如果 onFulfilled() 或者 onRejected() 函数返回一个 promise,则 then() 应该返回一个 “locked in” 的新 promise 以匹配这个 promise 的状态。例如:
下面是可以返回 promise 的 .then() 函数实现,这样就可以进行链式调用。
现在 then() 返回一个 promise,但是还需要完成一些工作:如果 onFulfilled() 返回一个 promise,resolve() 要能够正确处理。所以 resolve() 函数需要在 then() 递归调用,下面是更新后的 resolve() 函数:
为了简单起见,上面的例子省略了一旦 promise 被锁定用以匹配另一个 promise 时,调用 resolve() 或者 reject() 是无效的关键细节。在上面的例子中,你可以 resolve() 一个 pending 的 promise ,然后抛出一个错误,然后 res.then(resolve, reject) 将会无效。这仅仅是一个例子,而不是 ES6 promise 规范的完全实现。
上面的代码说明了 resolved 的 promise 和 fulfilled 的 promise 之间的区别。这种区别是微妙的,并且与 promise 链式调用有关。resolved 不是一种真正的 promise 状态,但它是ES6规范中定义的术语。当对一个已经 resolved 的 promise 调用 resolve(),可能会发生以下两件事之一:
在调用
resolve(v)时,如果v不是一个 promise ,那么 promise 立即成为 fulfilled。在这种简单的情况下,resolved 和 fulfilled 就是一样的。在调用
resolve(v)时,如果v是另一个 promise,那么这个 promise 一直处于 pending 直到v调用 resolve 或者 reject。在这种情况下, promise 是 resolved 但处于 pending 状态。
与 Async/Await 一起使用
关键字 await 会暂停执行一个 async 函数,直到等待的 promise 变成 settled 状态。现在我们已经有了一个简单的自制 promise 库,看看结合使用 async/await 中时会发生什么。向 then() 函数添加一个 console.log() 语句:
现在,我们来 await 一个 MyPromise 的实例,看看会发生什么。
注意上面的 .catch() 调用。catch() 函数是 ES6 promise 规范的核心部分。本文不会详细讲述它,因为 .catch(f) 相当于 .then(null, f),没有什么特别的内容。
以下是输出内容,注意 await 隐式调用 .then() 中的 onFulfilled() 和 onRejected() 函数,这是 V8 底层的 C++ 代码(native code)。此外,await 会一直等待调用 .then() 直到下一个时序。
更多
async/await 是非常强大的特性,但掌握起来稍微有点困难,因为需要使用者了解 promise 的基本原则。 promise 有很多细节,例如捕获处理器函数中的同步错误,以及 promise 一旦解决就无法改变状态,这使得 async/await 成为可能。一旦对 promise 有了充分的理解,async/await 就会变得容易得多。
Last updated