前端面试之手写Promise

前言

最近老是听到前端面试要求手写promise,promise做为解决前端回调地狱的方案被普遍运用在前端异步调用上,nodejs更是出了一版返回promise IO接口,因此不管从面试仍是实际工做中熟悉promise都是一个合格前端必备技能。javascript

分析Promise

咱们知道要实现一个功能,首先要了解需求是什么,实现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

  • then接收两个函数,咱们暂且称它为成功回调和错误回调,分别在fullFilled和rejected的时候执行
  • 当promise状态已是决议状态时当即执行对应的成功回调/错误回调
  • 返回一个新的promise,新的promise在成功回调/错误回调执行完成后决议,并使用回调的返回做为决议结果
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/…异步

欢迎阅读个人其余文章:函数

相关文章
相关标签/搜索