最近老是听到前端面试要求手写promise,promise做为解决前端回调地狱的方案被普遍运用在前端异步调用上,nodejs更是出了一版返回promise IO接口,因此不管从面试仍是实际工做中熟悉promise都是一个合格前端必备技能。javascript
咱们知道要实现一个功能,首先要了解需求是什么,实现Promise也是如此。前端
那么Promise到底提供了哪些功能?咱们先把Promise中经常使用的方法列出来java
// 使用Promise的标准开头 new Promise(),可见Promise是一个类 class Promise { /** * 经常使用方式Promise.resolve(something).then(...) * 可见Promise.resolve返回一个新的Promise实例 **/ static resolve() {} // 同上 static reject() {} /** * 构造函数接收一个函数做为入参 * 该函数会接收resolve,reject做为入参,而且当即执行 * Promise将在fn调用resolve/reject后决议 */ constructor(fn) {} /** * 根据constructor咱们能够推断Promise有三个状态分别是 * pending,fullfilled,rejected * 咱们须要一个内部变量来保存promise的当前状态 */ _status = 'pending' // promise的初始状态为pending /** * 咱们须要一个变量保存promise的决议结果 * 考虑 new Promise((resolve) => resolve(2)); **/ _result /** * 考虑一下resolve作了什么事情 * 其实resolve改变promise的状态并将入参做为回调函数的入参 **/ resolve(ret) {} // 同上 reject(ret) {} /** * then称得上是promise的核心方法,到底then作了什么,咱们考虑一下 * then会接收两个函数,在promise的状态发生改变后会调用对应的函数 * 因此在这里then的做用应当是个事件注册器。 * 须要注意的是then是能屡次调用的 * const promise = new Promise(fn) * promise.then(fn1) * promise.then(fn2) * 另外then是支持链式调用的,如promise.then(fn3).then(fn4) * 因此调用then还应当返回一个promise对象 **/ then(fn, fn2) {} // 描述完then后,咱们发现咱们须要两个回调队列来保存使用then注册的回调 _successCallback = [] _errorCallback = [] // 咱们再定义一个内部方法来异步执行这些回调函数 // 使用入参type区分执行successCallback仍是errorCallback _runCallbackAsync(type) {} }
分析完Promise的核心功能后,让咱们依次开始实现这些接口。node
// 先不看静态方法和实例方法,从构造器开始实现 class Promise { ... constructor(fn) { // 根据以前描述构造器做用主要是运行传入的fn // 将fn放在try catch中运行,防止fn执行出错 try { //new Promise((resolve, reject) => {}) fn(this.resolve.bind(this), this.reject.bind(this)) } catch(e) { this.reject(e) } } // resolve调用以后改变后promise的状态,而且异步执行callback resolve(result) { // promise的状态不是pending,说明该promise已经调用过reject/resolve if(this._status !== 'pending') throw new Error('重复决议promise') // 保存决议结果 this._result = result // 异步执行callback this._runCallbackAsync('success') } // reject也是同理 reject(err) { if(this._status !== 'pending') throw new Error('重复决议promise') this._result = err // 这里咱们执行错误回调 this._runCallbackAsync('error') } ... }
在写promise.then以前咱们从新考虑下这个方法到底作了什么?咱们逐步实现这个功能点。git
class Promise { ... // 省略先后代码 then(fn1, fn2) { // 支持链式调用,当即回复一个新的promise对象 return new Promise((resolve, reject) => { /** 而后咱们要将对应的回调函数推入事件队列,他们将在本个promise * 决议后由_runCallbackAsync执行。 * 但执行完对应事件后须要将执行结果传到下一个promise中 * 因此咱们须要对回调函数进行小小的处理 */ // 接收决议结果 const successCallBack = (result) => { try { // 将promise的决议结果传递给回调函数去执行,并把回调函数的结果做为下一个promise的决议结果 resolve(fn1(result)) } catch(e) { // 若是执行出错,应该将错误信息传递给promise reject(e) } } const errorCallback = (e) => { try { reject(fn2(e)) } catch(err) { reject(err) } } // 将回调函数推入事件队列 if (fn1 && this._status !== 'error') this._successCallback.push(fn1) if (fn2 && this._status !== 'success') this._errorCallback.push(fn2) // 若是promise已经决议,则当即执行相应的回调 if(this._status === 'success') this._runCallbackAsync('success') if(this._status === 'error') this._runCallbackAsync('error') }) } // 实现 _runCallbackAsync(type) { let eventQueue; if(type === 'error') eventQueue = this._errorCallback; else eventQueue = this._successCallback; // 执行回调, 使用settimeout模拟异步执行 setTimeout(() => { eventQueue.forEach(callback => callback(this._result)); // 清空事件队列,两个事件队列都须要删除,由于promise决议后状态不可变动,决议执行完应当清空全部队列,以解除引用关系 this._errorCallback.length = 0; this._successCallback.length = 0; }, 0) } ... }
到此Promise的核心方法已经实现完毕,剩下的方法咱们很容易使用现有方法进行实现,例如:github
class Promise { ··· catch(fn) { return this.then(undefined, fn); } static resolve(any) { // 使用鸭子类型去判断any的类型 if ( typeof any.then === 'function' typeof any.catch === 'function' ... ) return any; // 若是不是一个promise则返回一个新的promise return new Promise((resolve) => { resolve(any) }) } ··· }
因此实现promise关键点在于,理解promise只决议一次,有三个状态(pending,fullFilled,rejected),then/catch支持链式调用(返回一个新的promise),而且理解then/catch本质是个事件注册器,相似于then = subscribe('resolve', callback),理解这些本身手动实现一个promise仍是不难的。面试
最后的最后:刚开始写技术文章,若是有错漏但愿可以不吝指出,若是以为写的还不错点个赞是对我最大的支持,谢谢。promise
最后附上源码连接:https://github.com/MinuteWong...异步