最近老是听到前端面试要求手写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
最后附上源码连接:github.com/MinuteWong/…异步
欢迎阅读个人其余文章:函数