手写 Promise 源码真的可以这么简单

74 阅读11分钟

# 前言

相信大家对Promise都不陌生,它是异步编程新的解决方案,主要解决了回调地域的问题。具体来讲Promises是一个构造函数,而promise对象用来封装一个异步操作并可以获得其结果。

在正式开始手写源码之前,我们先了解几个小知识点:

1、实例对象与函数对象的区别

实例对象:使用new函数产生的对象称为实例对象,简称对象。
函数对象:将函数作为对象使用时称为函数对象。

2、同步回调与异步回调

同步回调:立即执行,完全执行完了才结束且不会放入回调队列中。
异步回调:不会立即执行且会放入回调队列中将来执行。

3、错误类型、处理、对象

错误类型

  1. Error:所有错误类型的父类型
  2. ReferenceError:引用的变量不存在
  3. TypeError:数据类型不正确
  4. RangeError:数据值不在其所允许范围内
  5. SyntaxError:语法错误

错误处理

  1. 捕获错误:try...catch
  2. 抛出错误:thorw new Error('自定义错误信息')

错误对象

  1. message:错误相关信息
  2. stack:函数调用栈记录

# Promise 的基本理解

Promise有三种状态,分别为:pending、resolvedrejected。且promise对象的状态只能改变一次,即pending=>resolvedpending=>rejected

但不管成功还是失败,都会有一个结果数据,成功的结果数据一般称为value,失败的结果数据一般称为reason

当我们得到一个promise对象的时候,说明异步任务已经执行,即执行器是同步执行的。

相比与纯回调函数来说Promise的指定回调函数的方式更加灵活。一般纯函数必须在启动异步任务之前指定回调,而Promise的指定回调函数可以在异步任务之后指定。

所谓的回调地域就是第二个异步任务以第一个异步任务的结果为条件,第三个异步任务以第二个异步任务的结果为条件,且每次的失败结果都需要单独处理,依次类推形成的连续的串联执行的形式称之为回调地域。

Promise的链式调用很好的解决了回调地域的问题,并且利用异常传透也可以方便的处理异常。

当然也可以使用await/async来最终解决回调地域问题,以此来弥补Promise虽解决了回调地域,但仍有回调函数的问题。

思考以下几个问题:

1、如何改变Promise的状态?

  1. resolve(value):状态由pending ==> resolved
  2. reject(reason):状态由pending ==> rejected
  3. 抛出异常:状态由pending ==> rejected

2、一个Promise指定多个成功/失败的回调函数都会调用吗?

  1. Promise变为对应的状态时都会调用

3、改变Promise状态和指定回调函数谁先谁后?

  1. 都有可能,正常情况下是先指定回调在改变状态,但也可以先改变状态再指定回调

4、如何先指定状态再指定回调?

  1. 在执行器中直接调用resolve()/reject()
  2. 延迟更长的时间再调用then()

5、什么时候才能得到数据?

  1. 如果先指定的回调,那当状态发生改变时,回调函数就会调用继而得到数据
  2. 如果先改变的状态,那当指定回调时,回调函数就会调用继而得到数据

6、Promsie.then()返回的新Promise结果状态由什么决定?

then()指定的回调函数执行的结果决定,具体如下:

  1. 如果抛出异常,新Promise变为rejectedreason为抛出的异常
  2. 如果凡是的是非Promise的任意值,新Promise变为resolvedvalue为返回的值
  3. 如果返回的是另一个新Promise,此Promise的结果会成为新Promise的结果

7.Promise如何实现异常传透?

  1. 当使用Promise.then链式调用时,可在最后指定失败的回调
  2. 前面任何操作出了异常,都会传到最后是的回调中处理,也就是说若没写失败的处理函数,则相当于写了reason => {throw reason}

8.如何中断Promise

  1. 要想在Promise.then链式调用时,在中间中断,不在调用后面的回调函数,可以在回调函数中返回一个pending状态的Promise对象。

# Promise 的基本结构

正如我们了解到的一样,Promise大概做了以下几件事:

  1. 是一个构造函数并且暴露给window
  2. 有两个实例方法.then.catch
  3. 有四个函数对象方法resole、reject、all 和 race
(function(window) {
    /**
     * 自定义 Promsie 函数模块
     */
    class MyPromise {
        /**
         * 执行器函数
         * @param {*} excutor 同步执行的函数
         */
        constructor(excutor) { };
        
        /**
         * Promise原型对象方法:then
         * @param {*} onResolved 成功的回调
         * @param {*} onRejeced  失败的回调
         * @return 返回一个新的Promise对象
         */
        then(onResolved, onRejeced) {  };

        /**
         * Promise原型对象方法:catch
         * @param {*} onRejeced 失败的回调
         * @return 返回一个新的Promise对象
         */
        catch = function(onRejeced) {  }
    }

    /**
     * Promise 函数对象的:resolve
     * @param {*} value
     */
    MyPromise.resolve = function(value) {  };

    /**
     * Promise 函数对象的:reject
     * @param {*} reason
     */
    MyPromise.reject = function(reason) {  };

    /**
     * Promise 函数对象的:all
     * @param {*} Promises
     */
    MyPromise.all = function(Promises) {  };

    /**
     * Promise 函数对象的:race
     * @param {*} Promises
     */
    MyPromise.race = function(Promises) {   }    

    // 暴露给window
    window.MyPromise = MyPromise;
})(window)

# 完善 执行器 函数

咱们分下以下执行器函数都做了哪些事:

  1. 接收两个函数,分别为resolvereject
  2. 初始化状态status的值为pending
  3. 创建一个data用于存储Promise对象的结果数据
  4. 创建一个callbacks数组用来存储回调函数
  5. 执行这个执行器函数

为了方便使用,我们把状态的三个值定义在最外边,所以大体结构如下:

(function(window) {
    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';
    /**
     * 自定义 Promsie 函数模块
     */
    class MyPromise {
        /**
         * 执行器函数
         * @param {*} excutor 同步执行的函数
         */
        constructor(excutor) {
            const that = this;
            // 给 Promise 对象指定一个 status 属性,初始值为 pending
            this.status = PENDING;
            // 给 Promise 对象指定一个 用于存储结果数据的 data 属性
            this.data = undefined;
            // 每个元素的结构 { onResolved(){}, onRejected(){} }
            this.callbacks = [];

            // 定义一个 成功的 回调
            function resolve(value) {   }

            // 定义一个 失败的 回调
            function reject(reason) {  }
            
            // 立即同步执行 excutor
            excutor(resolve, reject);
        };
    }
    // 暴露给window
    window.MyPromise = MyPromise;
})(window)

具体分析下 resolve 函数做了哪些事

  1. 将状态改为resolved
  2. 保存value数据
  3. 如果有待执行的callback函数,立即异步执行回调函数onResolved
// 定义一个 成功的 回调
function resolve(value) {
    // 如果当前状态不是 pending , 则直接结束
    if (that.status !== PENDING) return;

    // 1. 将状态改为 resolved
    that.status = RESOLVED;
    // 2. 存储 value 数据
    that.data = value;
    // 3. 如果有待执行的 callbacks 函数,则立即异步执行回调函数
    if (that.callbacks.length > 0) {
        setTimeout(() => {
            // 放入队列中执行所有成功的回调
            that.callbacks.forEach(callbacksObj => {
                callbacksObj.onResolved(value);
            });
        })
    }
}

具体分析下 reject 函数做了哪些事

将状态改为rejected 保存reasons数据 如果有待执行的callback函数,立即异步执行回调函数onRejected

// 定义一个 失败的 回调
function reject(reason){
    // 如果当前状态不是 pending , 则直接结束
    if(that.status !== PENDING) return;

    // 1. 将状态改为 rejected
    that.status = REJECTED;
    // 2. 存储 reason 数据
    that.data = reason;
    // 3. 如果有待执行的 callbacks 函数,则立即异步执行回调函数
    if(that.callbacks.length > 0){
        setTimeout(()=>{
            // 放入队列中执行所有失败的回调
            that.callbacks.forEach(callbacksObj => {
                callbacksObj.onRejected(reason);
            });
        })
    }
}

我们在执行执行器函数时即执行resolve或reject函数有可能会报错,所以我们必须捕获一下,完整代码如下:

/**
 * 执行器函数
 * @param {*} excutor 同步执行的函数
 */
constructor(excutor) {
    const that = this;
    // 给 Promise 对象指定一个 status 属性,初始值为 pending
    this.status = PENDING;
    // 给 Promise 对象指定一个 用于存储结果数据的 data 属性
    this.data = undefined;
    // 每个元素的结构 { onResolved(){}, onRejected(){} }
    this.callbacks = [];

    // 定义一个 成功的 回调
    function resolve(value) {
        // 如果当前状态不是 pending , 则直接结束
        if (that.status !== PENDING) return;

        // 1. 将状态改为 resolved
        that.status = RESOLVED;
        // 2. 存储 value 数据
        that.data = value;
        // 3. 如果有待执行的 callbacks 函数,则立即异步执行回调函数
        if (that.callbacks.length > 0) {
            setTimeout(() => {
                // 放入队列中执行所有成功的回调
                that.callbacks.forEach(callbacksObj => {
                    callbacksObj.onResolved(value);
                });
            })
        }
    }

    // 定义一个 失败的 回调
    function reject(reason) {
        // 如果当前状态不是 pending , 则直接结束
        if (that.status !== PENDING) return;

        // 1. 将状态改为 rejected
        that.status = REJECTED;
        // 2. 存储 reason 数据
        that.data = reason;
        // 3. 如果有待执行的 callbacks 函数,则立即异步执行回调函数
        if (that.callbacks.length > 0) {
            setTimeout(() => {
                // 放入队列中执行所有失败的回调
                that.callbacks.forEach(callbacksObj => {
                    callbacksObj.onRejected(reason);
                });
            })
        }
    }

    // 立即同步执行 excutor
    try {
        excutor(resolve, reject)
    } catch (error) {
        // 如果执行器抛出异常,则 Promise 对象变为 rejected 状态
        reject(error);
    }
};

# 完善 then 函数

首先我们来看下then函数都做做了哪些事

  1. 返回一个新的Promise对象
  2. 判断当前状态:
    (1).如果抛出异常,returnPromise就会失败,reason就是error
    (2).如果回调函数返回的是非PromisereturnPromise就会成功,value就是返回值
    (3).如果回调函数是PromisereturnPromise结果就是这个Promise的结果
/**
 * Promise原型对象方法:then
 * @param {*} onResolved 成功的回调
 * @param {*} onRejeced  失败的回调
 * @return 返回一个新的Promise对象
 */
then(onResolved, onRejeced) {
    let that = this;

    // 向后传递成功的 value
    onResolved = typeof onResolved === 'function' ? onResolved : value => value;
    // 指定默认的失败回调,向后传递失败的 reason
    onRejeced = typeof onRejeced === 'function' ? onRejeced : reason => { throw reason };

    // 返回一个新的 Promise 对象:根据执行的结果,改变 return 的 Promise 的状态
    return new MyPromise((resolve, reject) => {
        // 调用指定的函数处理
        // 分三种情况:
        // 1.如果回调函数返回的是 Promise,return 的 Promise 结果就是这个 Promise 的结果
        // 2.如果回调函数返回的不是 Promise,return 的 Promise 就会成功,value 就是返回的值
        // 3.如果抛出异常,return 的 Promise 就会失败,reason 就是 error
        function handle(callback) {
            try {
                // 得到回调函数的结果
                const result = callback(that.data);
                // 判断 result 的类型
                if (result instanceof MyPromise) {
                    // 情况 1 :如果回调函数返回的是 Promise,return 的 Promise 结果就是这个 Promise 的结果
                    // 注意:想要拿到 Promise 的结果就只能 .then()
                    // 当 result 成功时,让 return 的 Promise 也成功
                    // 当 result 失败时,让 return 的 Promise 也失败
                    result.then(resolve, reject);
                } else {
                    // 情况 2 :如果回调函数返回的不是 Promise,return 的 Promise 就会成功,value 就是返回的值
                    resolve(result);
                }
            } catch (error) {
                // 情况 3 :如果抛出异常,return 的 Promise 就会失败,reason 就是 error
                reject(error);
            }
        }

        // 处理不同状态的 status
        if (that.status === PENDING) {
            // 如果状态为 pending 则保存回调函数
            this.callbacks.push({
                onResolved() {
                    handle(onResolved)
                },
                onRejeced() {
                    handle(onRejeced);
                }
            });
        } else if (that.status === RESOLVED) {
            // 如果状态为 resolved 则异步执行 onResolved 并改变 return 的 Promise 的状态
            setTimeout(() => {
                handle(onRejeced)
            });
        } else {
            // 如果状态为 rejected 则异步执行 onRejected 并改变 return 的 Promise 的状态
            setTimeout(() => {
                handle(onRejeced)
            })
        }
    })
};

# 完善 catch 函数

在完善then函数后,catch函数就相对简单些,它也是返回一个新的Promise对象,如下:、

 /**
 * Promise原型对象方法:catch
 * @param {*} onRejeced 失败的回调
 * @return 返回一个新的Promise对象
 */
catch = function(onRejeced) {
    return this.then(undefined, onRejeced);
}

测试我们写的代码:

 new MyPromise((resolve, reject) => {
    setTimeout(() => {
        // resolve(1);
        reject(2)
    })
}).then((value) => {
    console.log('onResolve 1 ' + value)
}, reason => {
    console.log('onRejected 1 ' + reason)
    // throw Error('报错了')
}).then((value) => {
    console.log('onResolve 2 ' + value)
}, reason => {
    console.log('onRejected 2 ' + reason)
})

# 完善 Promise.resolve/Promise.reject 方法

先来看下这俩方法都干了什么事:

resolve 方法:

  1. 返回一个新的Promise对象分两种情况
    (1). 如果valuePromise对象
    (2). 如果value不是Promsie对象
 
/**
 * @func Promise 函数对象的 resolve()
 * @param value 成功的回调函数 
 * @returns 返回一个指定结果 value 的成功的 Promise
 */ 
MyPromise.resolve = function (value) {
    // 返回一个成功 / 失败的 Promise
    return new MyPromise((resolve, reject)=>{
        // 判断 value 的类型
        if(value instanceof Promise){ // 如果是Promise
            value.then(resolve, reject)
        }else{ // 如果不是 Promise
            resolve(value)
        }
    })
}

reject 方法:

  1. 返回一个新的失败的Promise
 /**
 * @func Promise 函数对象的 reject()
 * @param reason 失败的回调函数
 * @returns 返回一个指定结果 reject 的失败的 Promise
 */ 
MyPromise.reject = function (reason) {
    // 返回一个失败的 Promise
    return new MyPromise((undefined,reject)=>{
        reject(reason)
    })
}

# 放法 Promise.all / Promise.race 的完善

先来看下这俩方法都干了什么事:

Promise.all 方法:

  1. 返回一个新的Promise
  2. 遍历所有的Promsie
    只要有一个失败,returnPromise就失败
    所有的都成功,returnPromise才成功
/**
 * @func Promise 函数对象的 all()
 * @param  promises 请求数组
 * @returns 返回一个 Promise
 * @returns 只有 Promise 都成功时才成功,只要有一个失败就失败,并返回失败的 Promise
 */
MyPromise.all = function(promises){
    // 用来保存所有成功value的数组
    const values = new Array(promises.length);
    // 用来保存成功Promise的数量
    let resolvedCount = 0;
    // 返回一个新的 Promise
    return new MyPromise((resolve, reject)=>{
        // 遍历 promises 获取每个 Promise 的结果
        promises.forEach((p,index)=>{
            // 防止 p 不是 Promise 所以包了一层
            MyPromise.resolve(p).then(
                value => {
                    resolvedCount++
                    // 将成功的 value 保存至 values
                    values[index] = value;
                    // 如果全部成功,return 的 Promise 的状态为成功
                    if(resolvedCount == promises.length){
                        resolve(values)
                    }
                },
                reason =>{
                    // 只要有一个失败了,return 的 Promise 就是失败
                    reject(reason);
                }
            )
        })
    })
}

Promise.race 方法:

  1. 返回一个新的Promise
  2. 其结果由第一个完成的Promise的结果决定
 /**
 * @func Promise 函数对象的 race()
 * @param  promises 请求数组
 * @returns 返回一个 Promise,其结果由第一个完成的 Promise 决定
 */
MyPromise.race = function(promises){
    // 返回一个新的 Promise
    return new MyPromise((resolve, reject)=>{
        // 遍历 promises 取出每个 Promsie 的结果
        promises.forEach(p => {
            // 防止 p 不是 Promise 所以包了一层
            MyPromise.resolve(p).then(
                value => {
                    // 一旦有一个成功了, return 的 Promise 就成功
                    resolve(value);
                },  
                reason =>{
                    // 一旦有一个失败了, return 的 Promise 就失败
                    reject(reason);
                }
            )
        })
    })
}

到这里Promise的整体逻辑框架就告一段落了,欢迎大家评论留言~~