手写Promise

        保姆级教学手写Promise,内容较多,但是详细。源码放在最后。

一、自定义Promise的初始结构的搭建

        在index.html中编写如下代码

        let p = new Promise((resolve, reject) => {
            resolve("执行成功");
        })
        p.then(result => {
            console.log(result);
        }, reason=>{
            console.log(reason);
        })

        创建一个promise.js文件在index.html中引入

        1.  Promise能够通过new的方式调用,所以Promise是构造函数的形式。

        2. 构造Promise实例的时候传过去了一个函数,使用executor(执行器函数接收这个形参)。

        3. Promise实例能够调用then方法,所以在构造函数的原型上要添加then方法。

        4. then方法接收两个函数。

// Promsie构造函数
function Promise(executor) {}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {}

二、resolve和reject的结构搭建

        1. executor函数是同步调用的,所以Promise实例一构造就会调用executor函数。

        2. executor函数中有两个参数,分别是resolve函数和reject函数。

        3. resolve和reject函数中接收一个参数用于存储数据。

// Promsie构造函数
function Promise(executor) {
    // resolve函数
    function resolve(data) {}
    // reject函数
    function reject(data) {}
    // 执行器函数是同步调用的,立即执行
    executor(resolve, reject);
}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {}

三、resolve和reject的代码实现

        1. Promise对象有两个属性,一个是PromiseState用于存储状态码,一个是PromiseResult用于存储返回结果。

        2. resolve和reject函数调用后会分别修改Promise实例对象的状态码为fulfilled和rejected。

        3. resolve和reject函数会修改Promise实例对象的结果。

// Promsie构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';  // 默认状态为pending
    this.PromiseResult = null;  // 默认值为空

    // resolve函数
    function resolve(data) {
        // 1. 修改Promise实例对象的状态(PromiseState)
        this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值(PromiseResult)
        this.PromiseResult = data;
    }
    // reject函数
    function reject(data) {}
    // 执行器函数是同步调用的,立即执行
    executor(resolve, reject);  // 调用执行器函数
}
 
// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {}

        打印Promise实例对象我们会发现状态码和存储值并未改变

        let p = new Promise((resolve, reject) => {
            resolve("执行成功");
        })
        console.log(p);

手写Promise_第1张图片

        如果我们打印一下resolve里面的this和Promise对象里的this可以发现问题,resolve函数里面的this指向的是window。

        这个时候我们需要将resolve函数和reject函数里面的this指向Promise实例对象。

// Promsie构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';  // 默认状态为pending
    this.PromiseResult = null;  // 默认值为空
    const _this = this;

    // resolve函数
    function resolve(data) {
        // 1. 修改Promise实例对象的状态(PromiseState)
        _this.PromiseState = 'fulfilled';
        // 2. 设置对象结果值(PromiseResult)
        _this.PromiseResult = data;
    }
    // reject函数
    function reject(data) {
        _this.PromiseState = 'rejected';
        _this.PromiseResult = data;
    }
    // 执行器函数是同步调用的,立即执行
    executor(resolve, reject);  // 调用执行器函数
}
 
// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {}

        问题就解决了, 不管是调用resolve还是reject都能成功显示结果

 四、throw抛出异常改变状态

        1. 在Promise对象中抛出错误会使Promise实例对象的状态码变为rejected。

        2. 使用try-catch可以捕获到错误。

        3. 抛出的错误信息将会变成Promise实例对象的存储数据。

        let p = new Promise((resolve, reject) => {
            // resolve("执行成功");
            // reject("执行失败");
            // 抛出异常
            throw new Error("异常");
        })
        console.log(p);

        我们在executor函数执行的时候使用try-cahtch检测是否抛错,抛错就调用reject函数改变Promise实例对象的状态和存储错误信息。

// Promsie构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending'; 
    this.PromiseResult = null;
    const _this = this;

    // resolve函数
    function resolve(data) {
        _this.PromiseState = 'fulfilled';
        _this.PromiseResult = data;
    }
    // reject函数
    function reject(data) {
        _this.PromiseState = 'rejected';
        _this.PromiseResult = data;
    }
    try {
        // 执行器函数是同步调用的,立即执行
        executor(resolve, reject); // 调用执行器函数   
    } catch (e) {
        reject(e);
    }
}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {}

五、Promise状态只能修改一次

        如下代码在正常的Promise中执行只会接收第一条resolve("执行成功")的结果,也就是PromiseState为"fulfilled",PromiseResult为"执行成功"。

        let p = new Promise((resolve, reject) => {
            resolve("执行成功");
            reject("执行失败");
            // throw new Error("异常");
        })
        console.log(p);

        但是我们写的代码第二次执行reject会覆盖掉上一次的PromiseState和PromiseResult

手写Promise_第2张图片

         所以需要在每次执行resolve函数和reject函数时要判断状态是否改变,如果状态已经改变过了就不能再次改变状态了。

// Promsie构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    const _this = this;

    // resolve函数
    function resolve(data) {
        // 判断状态
        if (_this.PromiseState !== 'pending') return;

        _this.PromiseState = 'fulfilled'; // resolved
        _this.PromiseResult = data;
    }
    // reject函数
    function reject(data) {
        if (_this.PromiseState !== 'pending') return;
        _this.PromiseState = 'rejected';
        _this.PromiseResult = data;
    }
    try {
        // 执行器函数
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {}

        我们再看刚才的运行结果就成功了。 

 六、then方法执行回调

        1. 在then方法中Promise状态为fulfilled执行第一个函数,rejected执行第二个函数。

        2. 在执行两个回调函数的时候传入了一个参数result(另外一个参数为reason)。

        3. 参入的参数为Promise实例对象的PromiseResult,如打印的result为"执行成功"。

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
    // 根据PromiseState判断调用哪个回调函数
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
}

七、异步任务回调的执行

        如果我们在Promise对象 中调用的是异步回调函数,那么在Promise对象的状态还未改变(pending)时就执行了then,但是在正常的Promise中会等异步执行完毕后再调用then的值。

        let p = new Promise((resolve, reject) => {
            setTimeout(()=>{
                resolve("执行成功");
            }, 1000);
        })
        console.log(p);

        p.then(result => {
            console.log(result);
        }, reason=>{
            console.log(reason);
        })

        我们怎么解决这个问题?

        1. 怎么判断是不是异步执行?

如果then方法中判断Promise对象的状态为pending,那么Promise中则进行的是异步操作。

        2. 什么时候执行the中的回调呢?

当resolve函数被调用的时候,也就是上面代码1秒过后就可以改变Promise的状态和值并执行then中的回调函数。

        3. resolve怎么能够知道then中的回调函数并调用呢?

在then中判断状态为pending时使用Promise中的属性将回调函数装起来。

        4. 现在已经有了then中的回调函数,resolve怎么知道什么时候需要执行then中的回调函数?

判断Promise对象是否存在回调函数,只有异步执行的时候才会在then中将回调函数保存起来。

// Promsie构造函数
function Promise(executor) {
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    // 声明属性,用于保存回调函数
    this.callback = {};
    const _this = this;

    // resolve函数
    function resolve(data) {
        if (_this.PromiseState !== 'pending') return;

        _this.PromiseState = 'fulfilled';
        _this.PromiseResult = data;

        // 调用成功的回调函数
        if (_this.callback.onResolved) {
            _this.callback.onResolved(data);
        }
    }
    // reject函数
    function reject(data) {
        if (_this.PromiseState !== 'pending') return;
        _this.PromiseState = 'rejected';
        _this.PromiseResult = data;
        if (_this.callback.onReject) {
            _this.callback.onReject(data);
        }
    }
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
    // 如果判断为pending状态,说明Promise中执行的是异步任务
    if (this.PromiseState === 'pending') {
        // 保存回调函数
        this.callback = {
            onResolved:onResolved,
            onRejected:onRejected
        }
    }
}

八、指定多个回调

        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("执行成功");
            }, 1000);
        })

        p.then(result => {
            console.log(1, result);
        }, reason => {
            console.log(1, reason);
        })
        p.then(result => {
            console.log(2, result);
        }, reason => {
            console.log(2, reason);
        })

        以上代码在正常的Promise中执行的结果是

        而我们的代码运行的结果是

        为什么会出现这样的问题呢?

        1. 进入第一个then方法时,Promise对象状态为pending,保存callback.onResult为console.log(1, result) 。

        2. 在第一个then执行完毕后,执行第二个then,再次调用then方法。

        3. 这时候Promise对象状态依然为pending,覆盖callback.onResult为console.log(2, result) 。

所以只会输出第二个then的结果而不会输出第一个then的结果。

         怎么解决这个问题呢?

        1. 我们将所有的回调函数都装起来,这时候callback就要使用数组而不能使用对象。

        2. 每次使用将then中pending状态的回调函数添加进数组中。

        3. 在每次调用resolve函数过后,通过遍历的方式调用所有的callback函数。

// Promsie构造函数
function Promise(executor) {
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    // 声明属性,保存回调函数
    this.callbacks = [];
    const _this = this;

    // resolve函数
    function resolve(data) {
        if (_this.PromiseState !== 'pending') return;

        _this.PromiseState = 'fulfilled'; // resolved
        _this.PromiseResult = data;
        // 遍历执行回调函数
        _this.callbacks.forEach(item => {
            item.onResolved(data);
        })
    }
    // reject函数
    function reject(data) {
        if (_this.PromiseState !== 'pending') return;
        _this.PromiseState = 'rejected';
        _this.PromiseResult = data;
        _this.callbacks.forEach(item => {
            item.onRejected(data);
        })
    }
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
    if (this.PromiseState === 'fulfilled') {
        onResolved(this.PromiseResult);
    }
    if (this.PromiseState === 'rejected') {
        onRejected(this.PromiseResult);
    }
    // 如果判断为pending状态,说明Promise中执行的是异步任务
    if (this.PromiseState === 'pending') {
        // 保存回调函数 
        this.callbacks.push({
            onResolved: onResolved,
            onRejected: onRejected
        })
    }
}

        当我们再次运行,执行结果就正确了

手写Promise_第3张图片

九、同步修改状态then方法并返回结果

        let p = new Promise((resolve, reject) => {
            resolve("执行成功");
        })

        const res = p.then(result => {
            console.log(result);
        }, reason => {
            console.log(reason);
        })
        console.log(res);

        以上代码我们使用正常的Promise执行then会返回一个新的Promise对象,因为then中没有设置返回值return,所以返回值为undefined。

手写Promise_第4张图片

        但是我们写的Promise调用then方法的时候并不会有返回值return,所以打印出来的res为undefined。

        如何解决这个问题呢?

        1. 在then方法中返回一个Promise对象。

        2. 根据上一个Promise对象实例不同的状态确定返回的Promise是调用的是resolve还是reject来改变自己的状态和返回值。

        3. resolve和reject中的参数就是回调函数的返回值return(如果回调函数没有返回值就为undefined)。

Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        if (this.PromiseState === 'fulfilled') {
            // 获取回调函数的执行结果
            let result = onResolved(this.PromiseResult);
            // 改变返回Promise的状态和结果值
            resolve(result);
        }
        if (this.PromiseState === 'rejected') {
            let result = onRejected(this.PromiseResult);
        }
        if (this.PromiseState === 'pending') {
            this.callbacks.push({
                onResolved: onResolved,
                onRejected: onRejected
            })
        }
    })
}

        当我们再次执行的时候就能得到then方法返回的一个正确的Promise结果

手写Promise_第5张图片

        但是如果我们在then函数中返回的是一个Promise对象呢?

        let p = new Promise((resolve, reject) => {
            resolve("执行成功");
        })

        const res = p.then(result => {
            console.log(result);
            return new Promise((resolve, reject) => {
                resolve("success");
            })
        }, reason => {
            console.log(reason);
        })
        console.log(res);

        正常的Promise会返回如下结果,返回的Promise对象的状态个和结果值覆盖先前的Promise状态和结果值。

手写Promise_第6张图片

         而我们的Promise会得到如下的结果,返回的Promise对象被变成了结果值保存起来。

手写Promise_第7张图片

        我们又怎么解决这个问题呢?

        1. 我们需要判断回调函数的返回结果是不是一个Promise对象。

        2. 如果是一个Promise对象就可以调用then方法,根据Promise对象中的状态和返回值判断调用哪一个then回调函数。

        3. 第一个回调中执行resolve,第二个回调中执行reject。

Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        if (this.PromiseState === 'fulfilled') {
            // 获取回调函数的执行结果
            let result = onResolved(this.PromiseResult);
            if (result instanceof Promise) {
                // 如果是 Promise 类型的对象
                result.then(res => {
                    resolve(res);
                }, reason => {
                    reject(reason);
                })
            } else {
                // 结果的对象状态为成功
                resolve(result); 
            }
        }
        if (this.PromiseState === 'rejected') {
            onRejected(this.PromiseResult);
        }
        if (this.PromiseState === 'pending') {
            this.callbacks.push({
                onResolved: onResolved,
                onRejected: onRejected
            })
        }
    })
}

        看一下效果非常成功

手写Promise_第8张图片

十、异步修改状态then方法结果返回

        如果我们在Promise对象中使用异步调用会出现什么结果

        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("执行成功");
            }, 1000);
        })

        const res = p.then(result => {
            return "then中返回的Promise";
        }, reason => {
            console.log(reason);
        })
        console.log(res);

        你可能会疑惑上一次异步调用(七、异步任务回调的执行)中then都能正常操作,为什么这一次就会出问题。

手写Promise_第9张图片

        分析一下:

        1. Promise对象中进行的时异步调用,要1秒钟后才会执行resolve函数去改变Promise对象的状态和结果值。

        2. 到达then函数的时候Promise状态值还是为pending。

        3. then函数现在返回的是一个Promise对象,而在状态为pending的时候并没有对返回的Promise对象做任何改变Promise对象的操作,所以状态依旧为pending,结果值为null。

        4. 注意这里的输出"执行成功"是p这个初始的Promise对象的resolve函数调用then执行的,和返回的新Promise对象没有关系。

        解决方法:

        1. 在异步调用情况下,then函数保存回调函数的同时设置返回Promise的状态和返回值。

        2. 返回的Promise对象的结果值是then中回调函数的返回值(return),这个时候回调函数就需要调用获取返回值。

        // 如果判断为pending状态,说明Promise中执行的是异步任务
        if (this.PromiseState === 'pending') {
            // 保存回调函数 
            this.callbacks.push({
                onResolved: function () {
                    let result = onResolved(this.PromiseResult);
                    resolve(result);
                },
                onRejected: onRejected
            })
        }

        这个时候返回的Promise就正确了

手写Promise_第10张图片

        接下来考虑then中返回的是Promise对象的情况(这部分就可以当做同步来处理)

        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("执行成功");
            }, 1000);
        })

        const res = p.then(result => {
            // return "then中返回的Promise"
            return new Promise((resolve, reject)=>{
                resolve("then中的Promise")
            })
        }, reason => {
            console.log(reason);
        })
        console.log(res);
        // 如果判断为pending状态,说明Promise中执行的是异步任务
        if (this.PromiseState === 'pending') {
            // 保存回调函数 
            this.callbacks.push({
                onResolved: function () {
                    let result = onResolved(this.PromiseResult);
                    if (result instanceof Promise) {
                        result.then(res => {
                            resolve(res)
                        }, reason => {
                            reject(reason)
                        })
                    } else {
                        resolve(result);
                    }
                },
                onRejected: onRejected
            })
        }

        运行结果是没有问题的

手写Promise_第11张图片

        但是我们在then中抛出错误还是会出错,所以我们要在回调函数中使用try-chatch监测错误

        // 如果判断为pending状态,说明Promise中执行的是异步任务
        if (this.PromiseState === 'pending') {
            this.callbacks.push({
                onResolved: function () {
                    try {
                        let result = onResolved(this.PromiseResult);
                        if (result instanceof Promise) {
                            result.then(res => {
                                resolve(res)
                            }, reason => {
                                reject(reason)
                            })
                        } else {
                            resolve(result);
                        }
                    } catch (e) {
                        reject(e);
                    }
                },
                onRejected: onRejected
            })
        }

十一、then方法完善和优化

         我们刚才一直在为then的第一个回调进行操作,接下来对第二个回调进行补全。以下是then函数完整的代码。

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
    return new Promise((resolve, reject) => {
        if (this.PromiseState === 'fulfilled') {
            let result = onResolved(this.PromiseResult);
            if (result instanceof Promise) {
                // 如果是 Promise 类型的对象
                result.then(res => {
                    resolve(res);
                }, reason => {
                    reject(reason);
                })
            } else {
                // 结果的对象状态为成功
                resolve(result);
            }
        }
        if (this.PromiseState === 'rejected') {
            let result = onRejected(this.PromiseResult);
            if (result instanceof Promise) {
                result.then(res => {
                    resolve(res);
                }, reason => {
                    reject(res);
                })
            } else {
                reject(result);
            }
        }
        // 如果判断为pending状态,说明Promise中执行的是异步任务
        if (this.PromiseState === 'pending') {
            // 保存回调函数 
            this.callbacks.push({
                onResolved: function () {
                    try {
                        let result = onResolved(this.PromiseResult);
                        if (result instanceof Promise) {
                            result.then(res => {
                                resolve(res)
                            }, reason => {
                                reject(reason)
                            })
                        } else {
                            resolve(result);
                        }
                    } catch (e) {
                        reject(e);
                    }
                },
                onRejected: onRejected
            })
        }
    })
}

        但是我们会发现在每一个状态中都有重复的操作——判断返回值是否为Promise,并为Promise设置状态和返回值。

        这个时候我们就要封装一个函数,方便我们代码的后期可维护性。

        1. 在返回的Promise对象中封装callback函数,直接从下面复制一个结构放在函数中即可。

        2. 传入一个参数用于定义调用onResolved函数还是onRejected函数。

        // 封装函数
        function callback() {
            try {
                // 获取回调函数的执行结果
                let result = onResolved(this.PromiseResult);
                if (result instanceof Promise) {
                    // 如果是 Promise 类型的对象
                    result.then(res => {
                        resolve(res);
                    }, reason => {
                        reject(reason);
                    })
                } else {
                    // 结果的对象状态为成功
                    resolve(result);
                }
            } catch (e) {
                reject(e);
            }
        }

        但是我们注意,这里的this在一个函数中,指向的是window,我们要让它指向Promise实例对象才能用到正确的PromiseResult。这里不单独演示,会在下一次代码中做修改。

十二、then方法的异步执行

        let p = new Promise((resolve, reject) => {
            resolve("执行成功");
            console.log(111);
        })

        const res = p.then(result => {
            console.log(222);
        }, reason => {
            console.log(reason);
        })
        console.log(333);

        以上代码在正常的Promise中执行结果如下

         我们的代码运行是这样

        出现问题的原因是then方法中的代码执行是异步的,而Promise和全局下的代码是同步的(不了解同步和异步可以去该专栏下看相关文章),所以先输出111和333,最后输出222。

        解决问题的方法很简单,我们只需让then中的回调函数变为异步执行即可。

        我们这里用到的是微任务队列函数,一共有两种情况要加上该函数。

        1. Promise对象中为同步执行时,then中的回调函数直接执行。

        2. Promise对象中为异步执行时,then中的回调函数在resolve和reject调用后执行。

        所以我们现在就把Promise的构造函数和Promise的then方法写写完了,以下是完整代码。

// Promsie构造函数
function Promise(executor) {
    // 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    // 声明属性,保存回调函数
    this.callbacks = [];
    const _this = this;

    // resolve函数
    function resolve(data) {
        // 判断状态
        if (_this.PromiseState !== 'pending') return;

        _this.PromiseState = 'fulfilled';
        _this.PromiseResult = data;
        // 执行回调函数
        queueMicrotask(() => {
            _this.callbacks.forEach(item => {
                item.onResolved(data);
            })
        })
    }
    // reject函数
    function reject(data) {
        if (_this.PromiseState !== 'pending') return;
        _this.PromiseState = 'rejected';
        _this.PromiseResult = data;
        queueMicrotask(() => {
            _this.callbacks.forEach(item => {
                item.onRejected(data);
            })
        })
    }
    try {
        // 执行器函数是同步调用的,立即执行
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

// 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
    const _this = this;
    return new Promise((resolve, reject) => {
        // 封装函数
        function callback(type) {
            try {
                let result = type(_this.PromiseResult);
                if (result instanceof Promise) {
                    result.then(res => {
                        resolve(res);
                    }, reason => {
                        reject(reason);
                    })
                } else {
                    resolve(result);
                }
            } catch (e) {
                reject(e);
            }
        }

        // 根据PromiseState判断调用哪个回调函数
        if (this.PromiseState === 'fulfilled') {
            queueMicrotask(() => {
                callback(onResolved)
            });
        }
        if (this.PromiseState === 'rejected') {
            queueMicrotask(() => {
                callback(onRejected)
            });
        }
        // 如果判断为pending状态,说明Promise中执行的是异步任务
        if (this.PromiseState === 'pending') {
            // 保存回调函数 
            this.callbacks.push({
                onResolved: function () {
                    callback(onResolved);
                },
                onRejected: function () {
                    callback(onRejected);
                }
            })
        }
    })
}

你可能感兴趣的:(Node.js,javascript,开发语言,ecmascript,node.js)