花了两周,对手写 Promise 篇章进行了总结,一步一步更着书籍中做,也更熟悉了 Promise。
callback到Promise
微信小程序的 callback 接口可以使用具象的 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 wx.request({ url, data, method, header: { }, success: function (res ) { console .log(res.data) }, fail: function (res ) { console .error(res.data) } }) const promiseify = fn => args => new Promise ((resolve, reject ) => { args.success = function (res ) { return resolve(res) } args.fail = function (res ) { return reject(res) } }) const wxRequest = promiseify(wx.request)
Promise 初实现
一步一步完成,初步的 Promise
初见雏形
Promise 本质为一个构造函数,在 Promise/A+规范 称 Promise 的构造函数为 executor ,它是函数类型的参数。这个函数“自动”具有 resolve、reject 两个方法作为参数。
进一步解释,Promise 构造函数返回一个 Promise 对象实例,这个返回的 Promise 对象具有一个 then 方法。在 then 的方法中,调用者可以定义两个参数,分别是 onfulfilled 和 onrejected,他们都是函数类型的参数。其中,onfulfilled 通过参数可以获取 Promise 对象经过 resolve 处理后的值,onrejected 可以获取 Promise 对象经过 reject 处理后的值。通过这个值,可以处理异步操作完成后的逻辑。
1 2 function Promise (executor ) {}Promise .prototype.then = function (onfulfilled, onrejected ) {}
在使用 new 关键字调用 Promise 构造函数时,在合适的时机(往往是异步操作结束时)调用 executor 的参数
resolve,并将经过 resolve 处理的值作为 resolve 的参数执行,这个值便可以在后续的 then 方法的第一个函数参数(onfulfilled)中拿到。
reject,出现错误的情况,将错误信息作为 reject 的参数执行,这个错误信息可以在后续的 then 方法的第二个函数参数(onrejected)中执行。
综上,我们需要两个变量 分别存储 resolve 和 reject 处理后的值(根据 Promise 的唯一性,使用一个变量也能存储);同时还需要存在一个状态 ,表示 Promise 实例的状态(pending、fulfilled、rejected);最后提供 resolve 和 reject 方法,这两个方法需要作为 executor 的参数提供给开发者使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Promise (executor ) { this .status = 'pending' this .value = null this .reason = null const resolve = value => this .value = value const reject = reason => this .reason = reason executor(resolve, reject) } Promise .prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype ) { onfulfilled(this .value) onrejected(this .reason) }
then 放在 Promise 构造函数的原型上,不放在构造函数内部的原因? 涉及到了原型和原型链的知识,每个 Promise 实例的 then 方法逻辑都是一致的,实例在调用该方法的时候,可以通过原型(Promise.prototype)来调用,而不需要每次实例化都创建一个 then 方法,以便节省内存,显然更合适。
实现状态完善
要知道,一个 Promise 实例的状态只能从 pending 变成 fulfilled,或者从 pending 变成 rejected。状态一旦变更,就不能再次变化或者逆转。因此我们需要在 resolve 和 reject 添加状态的判断。
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 function Promise (executor ) { this .status = 'pending' this .value = null this .reason = null const resolve = value => { if (this .status === 'pending' ) { this .value = value this .status = 'fulfilled' } } const reject = reason => { if (this .status === 'pending' ) { this .reason = reason this .status = 'rejected' } } executor(resolve, reject) } Promise .prototype.then = function (onfulfilled, onrejected ) { onfufilled = typeof onfulfilled === 'function' ? onfulfilled : data => data onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error } if (this .status === 'fulfilled' ) { onfulfilled(this .value) } if (this .status === 'rejected' ) { onrejected(this .reason) } }
异步实现完善
这里完善的核心就是,在异步的情况下,也就是 status 为 'pending' 的时候,我们需要先收集对应的 onfulfilled 和 onrejected 方法,在异步结束后,在调用对应的任务。而且我们需要包装我们的 resolve 和 reject 函数,避免影响到外界的同步代码执行。保证 Promise 的决议异步执行。这里我们使用 setTimeout 实现,实际上,为了保证 Promise 为 microtasks,很多Promise的实现库用了 MutationObserver 来模拟 nextTick。
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 function Promise (executor ) { this .status = 'pending' this .value = null this .reason = null this .onFulfilledFunc = Function .prototype this .onRejectedFunc = Function .prototype const resolve = value => { if (value instanceof Promise ) { return value.then(resolve, reject) } setTimeout(() => { if (this .status === 'pending' ) { this .value = value this .status = 'fulfilled' this .onFulfilledFunc(this .value) } }) } const reject = reason => { setTimeout(() => { if (this .status === 'pending' ) { this .reason = reason this .status = 'rejected' this .onRejectedFunc(this .reason) } }) } executor(resolve, reject) } Promise .prototype.then = function (onfulfilled, onrejected ) { onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error } if (this .status === 'fulfilled' ) { onfulfilled(this .value) } if (this .status === 'rejected' ) { onrejected(this .reason) } if (this .status === 'pending' ) { this .onFulfilledFunc = onfulfilled this .onRejectedFunc = onrejected } }
细节完善
接下来我们需要实现的是,在实例变更之前添加多个 then 方法的收集工作。
上面一步中如果我们多次调用 then 方法,第二次的 then 方法中的 onFulfilledFunc 会覆盖第一个 then 方法中的 onFulfilledFunc。所以,需要所有的 onFulfilledFunc 存储到一个数组 onFulfilledArray 中,在当前 Promise 被决议时依次执行 onFulfilledArray 数组内的方法就可以了。对于 onRejectedFunc 同理。
第二个需要完善的细节部分是,在构造函数中如果出错,将会自动触发 Promise 实例状态变为 rejected,因此我们用 try...catch
对 executor 进行包裹。
1 2 3 4 5 try { executor(resolve, reject) } catch (e) { reject(e) }
到这我们就初步实现了基本的 Promise。并且实现的过程中,完成了以下几条重要的结论。
Promise 的状态具有凝固性
Promise 可以在 then 方法第二个参数中进行错误处理
Promise 实例可以添加多个 then 处理场景
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 const promise = new Promise ((reslove, rejcet ) => { setTimeout(() => { reslove('lucas' ) }, 2000 ) }) promise.then(data => { console .log(data) return `${data} next then` }) .then(data => { console .log(data) }) promise.then(data => { console .log(data) return new Promise ((resolve, reject ) => { resolve(`${data} nect then` ) }, 4000 ) }) .then(data => { console .log(data) })
通过上例中,我们可以看到,一个 Promise 的 then 方法是支持再次返回一个 Promise 实例的,也支持返回一个非 Promise 实例的普通值,并且返回的这个 Promise 实例或这个非 Promise 实例的普通值将会传给下一个 then 方法的 onfulfilled 函数或 onrejected 函数,这样,then方法就支持链式调用了。
初步实现
首先,让我们先考虑普通值的情况。完善阶段再考虑将 Promise 实例传递。 作为简单值来说,首先想到的就是用一个新的 Promise 实例,即 Promise2 来包装简单值,并将 Promise2 返回。
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 Promise .prototype.then = function (onfulfilled, onrejected ) { onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error } let promise2 if (this .status === 'fulfilled' ) { return promise2 = new Promise ((resolve, reject ) => { setTimeout(() => { try { let result = onfulfilled(this .value) resolve(result) } catch (e) { reject(e) } }) }) } if (this .status === 'rejected' ) { onrejected(this .reason) } if (this .stauts === 'pending' ) { this .onFulfilledArray.push(onfulfilled) this .onRejectedArray.push(onrejected) } }
同理将 this.status === 'rejected'
状态和 this.status === 'pending'
状态也要加入相同的逻辑。需要重要注意的是,this.status === 'pending'
分支中,需要收集对应的,onFulfilledArray 或 onRejectedArray。在异步处理结束后,依次处理收集的函数。所以实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if (this .status === 'pending' ) { return promise2 = new Promise ((resolve, reject ) => { this .onFulfilledArray.push(() => { try { let result = onfulfilled(this .value) resolve(result) } catch (e) { reject(e) } }) this .onRejectedArray.push(() => { try { let result = onrejected(this .reason) resolve(result) } catch (e) { reject(e) } }) }) }
完善实现
上一步,初步实现,已经实现了返回一个直接值情况,我们需要重构修改,let result = onfulfilled(this.value)
和 let result = onrejected(this.reason)
代码逻辑。我们抽象一个 resolvePromise 来统一处理。
1 2 3 4 5 6 const resolvePromise = (promise2, result, resolve, reject ) => { } resolvePromise(promise2, result, resolve, reject)
resolvePromise接收4个参数
promise2:返回的 Promise 实例。
result:onfulfilled 或 onrejected 函数的返回值。
resolve:promise2 的 resolve 方法。
reject:promise2 的 reject 方法。
需要注意的是,如果 promise 和 promise2 是同一个实例,需要抛出错误。详见,promise规范 2.3.1 章节
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 function Promise (executor ) { this .status = 'pending' this .value = null this .reason = null this .onFulfilledArray = [] this .onRejectedArray = [] const reslove = value => { if (value instanceof Promise ) { return value.then(resolve, reject) } setTimeout(() => { if (this .status === 'pending' ) { this .value = value this .status = 'fulfilled' this .onFulfilledArray.forEach(func => { func(value) }) } }) } const reject = reason => { setTimeout(() => { if (this .status === 'pending' ) { this .reason === reason this .status = 'rejected' this .onRejectedArray.forEach(func => { func(reason) }) } }) } try { executor(resolve, reject) } catch (e) { reject(e) } } const reslovePromise = (promise2, result, resolve, reject ) => { if (result === promise2) { reject(new TypeError ('error due to circular reference' )) } let consumed = false let thenable if (result instanceof Promise ) { if (result.status === 'pending' ) { result.then(function (data ) { resolvePromise(promise2, data, resolve, reject) }, reject) } else { result.then(resolve, reject) } return } let isComplexResult = target => (typeof target === 'function' || typeof target === 'object' ) && (target !== null ) if (isComplexResult(result)) { try { thenable = result.then if (typeof thenable === 'function' ) { thenable.call(result, function (data ) { if (consumed) { return } consumed = true return resolvePromise(promise2, data, resolve, reject) }, function (error ) { if (consumed) { return } consumed = true return reject(error) }) } else { resolve(result) } } catch (e) { if (consumed) { return } consumed = true return reject(true ) } } else { resolve(result) } } Promise .prototype.then = function (onfulfilled, onrejected ) { onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error } let promise2 if (this .status === 'fulfilled' ) { return promise2 = new Promise ((resolve, reject ) => { setTimeout(() => { try { let result = onfulfilled(this .value) resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }) }) } if (this .status === 'rejected' ) { return promise2 = new Promise ((resolve, reject ) => { setTimeout(() => { try { let result = onrejected(this .reason) resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }) }) } if (this .status = 'pending' ) { return promise2 = new Promise ((resolve, reject ) => { this .onFulfilledArray.push(value => { try { let result = onfulfilled(value) resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }) this .onRejectArray.push(reason => { try { let result = onrejected(reason) resolvePromise(promise2, result, resolve, reject) } catch (e) { reject(e) } }) }) } }
Promise 方法实现
看一段代码。
1 2 3 4 5 6 7 8 9 10 11 12 const promise = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('lucas' ) }) }) promise .then(null ) .then(data => { console .log(data) })
穿透实现
其含义为:给 then() 方法传递非函数值作为其参数时,实际上会被解析为 then(null),这时,上一个 Promise 对象的决议结果便会 "穿透" 到下一个 then 方法中。前面的写法中,已经给对应的函数加上了判断。
1 2 3 4 5 Promise .prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype ) { onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error } }
静态方法和其他实现
接下来会讲解以下方法的实现
Promise.prototype.catch
Promise.resolve
Promise.reject
Promise.all
Promise.race
Promise.prototype.catch
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const promise = new Promise ((resolve, reject ) => { setTimeout(() => { reject('lucas error' ) }, 2000 ) }) promise.then(data => { console .log(data) }).catch(error => { console .log(error) }) Promise .prototype.catch = function (catchFunc ) { return this .then(null , catchFunc) }
Promise.resolve
实现
MDN解释: Promise.resolve(value)
方法返回一个以给定值解析后的 Promise 实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Promise .resolve('data' ).then(data => { console .log(data) }) console .log(1 )Promise .resolve = function (value ) { return new Promise ((resolve, reject ) => { resolve(value) }) } Promise .reject = function (value ) { return new Promise ((resolve, reject ) => { reject(value) }) }
Promise.all 实现
MDN解释:Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内的所有 Promise 实例都 resolved 或参数中不包含 Promise 实例时完成回调;如果参数中的 Promise 实例有一个 rejected,则此实例回调失败,失败原因时第一个 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 const promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('lucas' ) }, 2000 ) }) const promise2 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('lucas' ) }. 2000 ) }) Promise .all([promise1, promise2]).then(data => { console .log(data) }) Promise .all = function (promiseArray ) { if (!Array .isArray(promiseArray)) { throw new TypeError ('The arguments should be an array' ) } return new Promise ((resolve, reject ) => { try { let resultArray = [] const length = promiseArray.length for (let i = 0 ; i < length; i++) { promiseArray[i].then(data => { resultArray.push(data) if (resultArray.length === length) { resolve(resultArray) } }, reject) } } catch (e) { reject(e) } }) }
Promise.race 实现
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 const promise1 = new Promise ((resolve, reject ) => { setTimeout(() => { resolve('lucas1' ) }, 2000 ) }) const promise2 = new Promise ((resolve, reject ) => { setTimeout(() => { reoslve('lucas2' ) }, 4000 ) }) Promise .race([promise1, promise2]).then(data => { console .log(data) }) Promise .race = function (promiseArray ) { if (!Array .isArray(promiseArray)) { throw new TypeError ('The arguments should be an array!' ) } return new Promise ((resolve, reject ) => { try { const length = promiseArray.length for (let i = 0 ; i < length; i++) { promiseArray[i].then(resolve, reject) } } catch (e) { reject(e) } }) }
引用