这是一道有着成熟的业界规范的 coding 题,完成这道题的前置知识就是要了解什么是 Promises/A+。
这道题的难点就在于它是有规范的,任何一个不满足所有规范条件的解答都是错误的。同时,成熟的规范也配套了成熟的测试用例,官方提供了 872 个测试用例针对规范中的所有条件一一进行检测,哪怕只有一条失败,那也是错误的解答。
而这道题的答题关键也恰恰是因为它是有规范的,只要我们对于规范了然于胸,那么编写代码自然也是水到渠成。因为官方规范提供了一个符合 Promises/A+ 规范的 Promise 应该具有的全部条件,并且在 Requirements 一节中结构清晰、逻辑充分的表述了出来,我们只需将规范中的文字转变为代码,就能够实现一个 Promises/A+ 规范的 Promise。
Promise
Promise 翻译过来就是承诺的意思,这个承诺会在未来有一个确切的答复,并且该承诺有三种状态,分别是:
- 等待中(pending)
- 完成了(resolved)
- 拒绝了(rejected)
Promise的一些特点
- Promise状态一旦从等待状态变成为其他状态就永远不能更改状态了。
1 2 3 4 5
| new Promise((resolve, reject) => { resolve('success') // 无效 reject('reject') })
|
- 当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的:
1 2 3 4 5 6
| new Promise((resolve, reject) => { console.log('new Promise') resolve('success') }) console.log('finifsh')
|
- Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
1 2 3 4 5 6 7 8
| Promise.resolve(1) .then(res => { console.log(res) return 2 }) .then(res => { console.log(res) })
|
- Promise 也很好地解决了回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:
1 2 3 4 5 6 7 8
| ajax(url) .then(res => { console.log(res) return ajax(url1) }).then(res => { console.log(res) return ajax(url2) }).then(res => console.log(res))
|
手写Promise
实现一个简易版的Promise
- 大体框架
1 2 3 4 5 6 7 8 9 10 11 12 13
| const PENDING = 'pending' const RESOLVED = 'resolved' const REJECTED = 'rejected'
function MyPromise(fn) { const that = this that.state = PENDING that.value = null that.resolvedCallbacks = [] that.rejectedCallbacks = [] // 待完善 resolve 和 reject 函数 // 待完善执行 fn 函数 }
|
- 首先我们创建了三个常量用于表示状态,对于经常使用的一些值都应该通过常量来管理,便于开发及后期维护
- 在函数体内部首先创建了常量 that,因为代码可能会异步执行,用于获取正确的 this 对象
- 一开始 Promise 的状态应该是 pending
- value 变量用于保存 resolve 或者 reject 中传入的值
- resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调,因为当执行完 Promise 时状态可能还是等待中,这时候应该把 then 中的回调保存起来用于状态改变时使用
- 接下来完善resolve 和 reject 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value)) } }
function reject(value) { if (that.state === PENDING) { that.state = REJECTED that.value = value that.rejectedCallbacks.map(cb => cb(that.value)) } }
|
这两个函数代码类似,就一起解析了
- 首先两个函数都得判断当前状态是否为等待中,因为规范规定只有等待态才可以改变状态
- 将当前状态更改为对应状态,并且将传入的值赋值给 value
- 遍历回调数组并执行
- 完成以上两个函数以后,我们就该实现如何执行 Promise 中传入的函数了
1 2 3 4 5
| try { fn(resolve, reject) } catch (e) { reject(e) }
|
实现很简单,执行传入的参数并且将之前两个函数当做参数传进去
接下来实现较为复杂的then函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| MyPromise.prototype.then = function(onFulfilled, onRejected) { const that = this onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r } if (that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled) that.rejectedCallbacks.push(onRejected) } if (that.state === RESOLVED) { onFulfilled(that.value) } if (that.state === REJECTED) { onRejected(that.value) } }
|
- 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
- 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传。
以上就是简单版 Promise 实现,接下来一小节是实现完整版 Promise 的解析,相信看完完整版的你,一定会对于 Promise 的理解更上一层楼。
符合 Promise/A+ 规范的 Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| function Promise(executor) { this.state = "pending"; this.onFulfilledCallback = []; this.onRejectedCallback = [];
const self = this;
function resolve(value) { setTimeout(function () { if (self.state === "pending") { self.state = "fulfilled"; self.data = value; for (let i = 0; i < self.onFulfilledCallback.length; i++) { self.onFulfilledCallback[i](value); } } }); }
function reject(reason) { setTimeout(function () { if (self.state === "pending") { self.state = "rejected"; self.data = reason; for (let i = 0; i < self.onRejectedCallback.length; i++) { self.onRejectedCallback[i](reason); } } }); }
try { executor(resolve, reject); } catch (reason) { reject(reason); } }
|
then方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
Promise.prototype.then = function (onFulfilled, onRejected) { const self = this;
let promise2; return (promise2 = new Promise(function (resolve, reject) { if (self.state === "fulfilled") { setTimeout(function () { if (typeof onFulfilled === "function") { try { const x = onFulfilled(self.data); promiseResolutionProcedure(promise2, x, resolve, reject); } catch (e) { reject(e); } } else { resolve(self.data); } }); } else if (self.state === "rejected") { setTimeout(function () { if (typeof onRejected === "function") { try { const x = onRejected(self.data); promiseResolutionProcedure(promise2, x, resolve, reject); } catch (e) { reject(e); } } else { reject(self.data); } }); } else if (self.state === "pending") {
self.onFulfilledCallback.push(function (promise1Value) { if (typeof onFulfilled === "function") { try { const x = onFulfilled(self.data); promiseResolutionProcedure(promise2, x, resolve, reject); } catch (e) { reject(e); } } else { resolve(promise1Value); } }); self.onRejectedCallback.push(function (promise1Reason) { if (typeof onRejected === "function") { try { const x = onRejected(self.data); promiseResolutionProcedure(promise2, x, resolve, reject); } catch (e) { reject(e); } } else { reject(promise1Reason); } }); } })); };
|