promise 是 ES6 中新增的一种异步解决方案,在平常开发中也常常能看见它的身影,例如原生的 fetch API 就是基于 promise 实现的。那么 promise 有哪些特性,如何实现一个具备 promise/A+ 规范的 promise 呢?javascript
首先咱们整理一下 promise 的一些基本特性和 API,完整的 promise/A+ 规范能够参考 【翻译】Promises/A+规范java
接下来咱们逐步实现一个具备 promise/A+ 规范的 promisejquery
先定义一个常量,表示 promise 的三个状态git
const STATE = { PENDING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rejected' } 复制代码
而后在 promise 中初始化两个参数 value 和 reason,分别表示状态为 fulfill 和 reject 时的值,接着定义两个函数,函数内部更新状态以及相应的字段值,分别在成功和失败的时候执行,而后将这两个函数传入构造函数的函数参数中,以下:es6
class MyPromise { constructor(fn) { // 初始化 this.state = STATE.PENDING this.value = null this.reason = null // 成功 const fulfill = (value) => { // 只有 state 为 pending 时,才能够更改状态 if (this.state === STATE.PENDING) { this.state = STATE.FULFILLED this.value = value } } // 失败 const reject = (reason) => { if (this.state === STATE.PENDING) { this.state = STATE.REJECTED this.reason = reason } } // 执行函数出错时调用 reject try { fn(fulfill, reject) } catch (e) { reject(e) } } } 复制代码
接下来初步实现一个 then 方法,当当前状态是 fulfulled 时,执行成功回调,当前状态为 rejected 时,执行失败回调:github
class MyPromise { constructor(fn) { //... } then(onFulfilled, onRejected) { if (this.state === STATE.FULFILLED) { onFulfilled(this.value) } if (this.state === STATE.REJECTED) { onRejected(this.reason) } } } 复制代码
这个时候一个简单的 MyPromise 就实现了,可是此时它还只能处理同步任务,对于异步操做却无能为力面试
要想处理异步操做,能够利用队列的特性,将回调函数先缓存起来,等到异步操做的结果返回以后,再去执行相应的回调函数。npm
具体实现来看,在 then 方法中增长判断,若为 pending 状态,将传入的函数写入对应的回调函数队列;在初始化 promise 时利用两个数组分别保存成功和失败的回调函数队列,并在 fulfill 和 reject 回调中增长它们。以下:数组
class MyPromise { constructor(fn) { // 初始化 this.state = STATE.PENDING this.value = null this.reason = null // 保存数组 this.fulfilledCallbacks = [] this.rejectedCallbacks = [] // 成功 const fulfill = (value) => { // 只有 state 为 pending 时,才能够更改状态 if (this.state === STATE.PENDING) { this.state = STATE.FULFILLED this.value = value this.fulfilledCallbacks.forEach(cb => cb()) } } // 失败 const reject = (reason) => { if (this.state === STATE.PENDING) { this.state = STATE.REJECTED this.reason = reason this.rejectedCallbacks.forEach(cb => cb()) } } // 执行函数出错时调用 reject try { fn(fulfill, reject) } catch (e) { reject(e) } } then(onFulfilled, onRejected) { if (this.state === STATE.FULFILLED) { onFulfilled(this.value) } if (this.state === STATE.REJECTED) { onRejected(this.reason) } // 当 then 是 pending 时,将这两个状态写入数组中 if (this.state === STATE.PENDING) { this.fulfilledCallbacks.push(() => { onFulfilled(this.value) }) this.rejectedCallbacks.push(() => { onRejected(this.reason) }) } } } 复制代码
接下来对 MyPromise 进行进一步改造,使其可以支持链式调用,使用过 jquery 等库应该对于链式调用很是熟悉,它的原理就是调用者返回它自己,在这里的话就是要让 then 方法返回一个 promise 便可,还有一点就是对于返回值的传递:promise
class MyPromise { constructor(fn) { //... } then(onFulfilled, onRejected) { return new MyPromise((fulfill, reject) => { if (this.state === STATE.FULFILLED) { // 将返回值传入下一个 fulfill 中 fulfill(onFulfilled(this.value)) } if (this.state === STATE.REJECTED) { // 将返回值传入下一个 reject 中 reject(onRejected(this.reason)) } // 当 then 是 pending 时,将这两个状态写入数组中 if (this.state === STATE.PENDING) { this.fulfilledCallbacks.push(() => { fulfill(onFulfilled(this.value)) }) this.rejectedCallbacks.push(() => { reject(onRejected(this.reason)) }) } }) } } 复制代码
实现到这一步的 MyPromise 已经能够支持异步操做、链式调用、传递返回值,算是一个简易版的 promise,通常来讲面试时须要手写一个 promise 时,到这个程度就足够了,完整实现 promise/A+ 规范在面试这样一个较短的时间内也不太现实。
到这一步的完整代码能够参考 promise3.js
promise/A+ 规范中规定,onFulfilled/onRejected 返回一个值 x,对 x 须要做如下处理:
TypeError
错误Promise
,则保持 then 方法返回的 promise 的值与 x 的值一致x.then
赋值给 then
并调用
then
是一个函数,则将 x 做为做用域 this
调用,并传递两个参数 resolvePromise
和 rejectPromise
,若是 resolvePromise
和 rejectPromise
均被调用或者被调用屡次,则采用首次调用并忽略剩余调用then
方法出错,则以抛出的错误 e 为拒因拒绝 promisethen
不是函数,则以 x 为参数执行 promise接下来对上一步实现的 MyPromise 进行进一步优化,使其符合 promise/A+ 规范:
class MyPromise { constructor(fn) { //... } then(onFulfilled, onRejected) { const promise2 = new MyPromise((fulfill, reject) => { if (this.state === STATE.FULFILLED) { try { const x = onFulfilled(this.value) generatePromise(promise2, x, fulfill, reject) } catch (e) { reject(e) } } if (this.state === STATE.REJECTED) { try { const x = onRejected(this.reason) generatePromise(promise2, x, fulfill, reject) } catch (e) { reject(e) } } // 当 then 是 pending 时,将这两个状态写入数组中 if (this.state === STATE.PENDING) { this.fulfilledCallbacks.push(() => { try { const x = onFulfilled(this.value) generatePromise(promise2, x, fulfill, reject) } catch(e) { reject(e) } }) this.rejectedCallbacks.push(() => { try { const x = onRejected(this.reason) generatePromise(promise2, x, fulfill, reject) } catch (e) { reject(e) } }) } }) return promise2 } } 复制代码
这里将处理返回值 x 的行为封装成为了一个函数 generatePromise
,实现以下:
const generatePromise = (promise2, x, fulfill, reject) => { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise')) } // 若是 x 是 promise,调用它的 then 方法继续遍历 if (x instanceof MyPromise) { x.then((value) => { generatePromise(promise2, value, fulfill, reject) }, (e) => { reject(e) }) } else if (x != null && (typeof x === 'object' || typeof x === 'function')) { // 防止重复调用,成功和失败只能调用一次 let called; // 若是 x 是对象或函数 try { const then = x.then if (typeof then === 'function') { then.call(x, (y) => { if (called) return; called = true; // 说明 y是 promise,继续遍历 generatePromise(promise2, y, fulfill, reject) }, (r) => { if (called) return; called = true; reject(r) }) } else { fulfill(x) } } catch(e) { if (called) return called = true reject(e) } } else { fulfill(x) } } 复制代码
promise/A+ 规范中还规定,对于 promise2 = promise1.then(onFulfilled, onRejected)
对于 then 方法作最后的完善,增长 setTimeout 模拟异步调用,增长对于 onFulfilled 和 onRejected 方法的判断:
class MyPromise { constructor(fn) { //... } then(onFulfilled, onRejected) { // 处理 onFulfilled 和 onRejected onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e } const promise2 = new MyPromise((fulfill, reject) => { // setTimeout 宏任务,确保onFulfilled 和 onRejected 异步执行 if (this.state === STATE.FULFILLED) { setTimeout(() => { try { const x = onFulfilled(this.value) generatePromise(promise2, x, fulfill, reject) } catch (e) { reject(e) } }, 0) } if (this.state === STATE.REJECTED) { setTimeout(() => { try { const x = onRejected(this.reason) generatePromise(promise2, x, fulfill, reject) } catch (e) { reject(e) } }, 0) } // 当 then 是 pending 时,将这两个状态写入数组中 if (this.state === STATE.PENDING) { this.fulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value) generatePromise(promise2, x, fulfill, reject) } catch(e) { reject(e) } }, 0) }) this.rejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason) generatePromise(promise2, x, fulfill, reject) } catch (e) { reject(e) } }, 0) }) } }) return promise2 } } 复制代码
实现 promise/A+ 规范的 promise 完整代码能够参考 promise4.js
如何知道你实现的 promise 是否遵循 promise/A+ 规范呢?能够利用 promises-aplus-tests 这样一个 npm 包来进行相应测试
这里对其余经常使用的 promise API 进行了实现
class MyPromise { constructor(fn) { //... } then(onFulfilled, onRejected) { //... } catch(onRejected) { return this.then(null, onRejected) } finally(callback) { return this.then(callback, callback) } } 复制代码
返回一个 resolved 状态的 Promise 对象
MyPromise.resolve = (value) => { // 传入 promise 类型直接返回 if (value instanceof MyPromise) return value // 传入 thenable 对象时,当即执行 then 方法 if (value !== null && typeof value === 'object') { const then = value.then if (then && typeof then === 'function') return new MyPromise(value.then) } return new MyPromise((resolve) => { resolve(value) }) } 复制代码
返回一个 rejected 状态的 Promise 对象
MyPromise.reject = (reason) => { // 传入 promise 类型直接返回 if (reason instanceof MyPromise) return reason return new MyPromise((resolve, reject) => { reject(reason) }) } 复制代码
返回一个 promise,一旦迭代器中的某个 promise 状态改变,返回的 promise 状态随之改变
MyPromise.race = (promises) => { return new MyPromise((resolve, reject) => { // promises 能够不是数组,但必须存在 Iterator 接口,所以采用 for...of 遍历 for(let promise of promises) { // 若是当前值不是 Promise,经过 resolve 方法转为 promise if (promise instanceof MyPromise) { promise.then(resolve, reject) } else { MyPromise.resolve(promise).then(resolve, reject) } } }) } 复制代码
返回一个 promise,只有迭代器中的全部的 promise 均变为 fulfilled,返回的 promise 才变为 fulfilled,迭代器中出现一个 rejected,返回的 promise 变为 rejected
MyPromise.all = (promises) => { return new MyPromise((resolve, reject) => { const arr = [] // 已返回数 let count = 0 // 当前索引 let index = 0 // promises 能够不是数组,但必须存在 Iterator 接口,所以采用 for...of 遍历 for(let promise of promises) { // 若是当前值不是 Promise,经过 resolve 方法转为 promise if (!(promise instanceof MyPromise)) { promise = MyPromise.resolve(promise) } // 使用闭包保证异步返回数组顺序 ((i) => { promise.then((value) => { arr[i] = value count += 1 if (count === promises.length || count === promises.size) { resolve(arr) } }, reject) })(index) // index 递增 index += 1 } }) } 复制代码
只有等到迭代器中全部的 promise 都返回,才会返回一个 fulfilled 状态的 promise,而且返回的 promise 状态老是 fulfilled,不会返回 rejected 状态
MyPromise.allSettled = (promises) => { return new MyPromise((resolve, reject) => { const arr = [] // 已返回数 let count = 0 // 当前索引 let index = 0 // promises 能够不是数组,但必须存在 Iterator 接口,所以采用 for...of 遍历 for(let promise of promises) { // 若是当前值不是 Promise,经过 resolve 方法转为 promise if (!(promise instanceof MyPromise)) { promise = MyPromise.resolve(promise) } // 使用闭包保证异步返回数组顺序 ((i) => { promise.then((value) => { arr[i] = value count += 1 if (count === promises.length || count === promises.size) { resolve(arr) } }, (err) => { arr[i] = err count += 1 if (count === promises.length || count === promises.size) { resolve(arr) } }) })(index) // index 递增 index += 1 } }) } 复制代码
本文若有错误,欢迎批评指正~
参考