MarukoMa 2020-09-02
前言
很多 JavaScript 的初学者都曾感受过被回调地狱支配的恐惧,直至掌握了 Promise 语法才算解脱。虽然很多语言都早已内置了 Promise ,但是 JavaScript 中真正将其发扬光大的还是 jQuery 1.5 对 $.ajax
的重构,支持了 Promise,而且用法也和 jQuery 推崇的链式调用不谋而合。后来 ES6 出世,大家才开始进入全民 Promise 的时代,再后来 ES8 又引入了 async 语法,让 JavaScript 的异步写法更加优雅。
今天我们就一步一步来实现一个 Promise,如果你还没有用过 Promise,建议先熟悉一下 Promise 语法再来阅读本文。
构造函数
在已有的 Promise/A+ 规范 中并没有规定 promise 对象从何而来,在 jQuery 中通过调用 $.Deferred()
得到 promise 对象,ES6 中通过实例化 Promise 类得到 promise 对象。这里我们使用 ES 的语法,构造一个类,通过实例化的方式返回 promise 对象,由于 Promise 已经存在,我们暂时给这个类取名为 Deferred
。
class Deferred { constructor(callback) { const resolve = () => { // TODO } const reject = () => { // TODO } try { callback(resolve, reject) } catch (error) { reject(error) } } }
构造函数接受一个 callback,调用 callback 的时候需传入 resolve、reject 两个方法。
Promise 的状态
Promise 一共分为三个状态:
pending
:等待中,这是 Promise 的初始状态;
fulfilled
:已结束,正常调用 resolve 的状态;
rejected
:已拒绝,内部出现错误,或者是调用 reject 之后的状态;
我们可以看到 Promise 在运行期间有一个状态,存储在 [[PromiseState]]
中。下面我们为 Deferred 添加一个状态。
//基础变量的定义 const STATUS = { PENDING: 'PENDING', FULFILLED: 'FULFILLED', REJECTED: 'REJECTED' } class Deferred { constructor(callback) { this.status = STATUS.PENDING const resolve = () => { // TODO } const reject = () => { // TODO } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }
这里还有个有意思的事情,早期浏览器的实现中 fulfilled 状态是 resolved,明显与 Promise 规范不符。当然,现在已经修复了。
内部结果
除开状态,Promise 内部还有个结果 [[PromiseResult]]
,用来暂存 resolve/reject 接受的值。
继续在构造函数中添加一个内部结果。
class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING const resolve = value => { this.value = value // TODO } const reject = reason => { this.value = reason // TODO } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }
储存回调
使用 Promise 的时候,我们一般都会调用 promise 对象的 .then
方法,在 promise 状态转为 fulfilled
或 rejected
的时候,拿到内部结果,然后做后续的处理。所以构造函数中,还需要构造两个数组,用来存储 .then
方法传入的回调。
class Deferred { constructor(callback) { this.value = undefined this.status = STATUS.PENDING this.rejectQueue = [] this.resolveQueue = [] const resolve = value => { this.value = value // TODO } const reject = reason => { this.value = reason // TODO } try { callback(resolve, reject) } catch (error) { // 出现异常直接进行 reject reject(error) } } }
resolve 与 reject
修改状态
接下来,我们需要实现 resolve 和 reject 两个方法,这两个方法在被调用的时候,会改变 promise 对象的状态。而且任意一个方法在被调用之后,另外的方法是无法被调用的。
new Promise((resolve, reject) => { setTimeout(() => { resolve('