手撕promise源码

手撕promise
promise非常重要,新加入的原生api和前端框架都大量使用了promise,promise已然成为前端的“水”和“电”。

promise解决了什么问题呢?promise解决的是异步编码风格的问题。

咱们来看,以前我们的异步代码长这样:

let fs = require('fs');
 
fs.readFile('./dellyoung.json',function(err,data){
  fs.readFile(data,function(err,data){
    fs.readFile(data,function(err,data){
      console.log(data)
    })
  })
})
层层嵌套,环环相扣,想拿到回调结果已经够费劲了,如果还想进行错误处理。。。那简直太难受了。

而promise出现后,这些问题迎刃而解:

let fs = require('fs');
 
function getFile(url){
  return new Promise((resolve,reject)=>{
    fs.readFile(url,function(error,data){
        if(error){
            reject(error)
        }
        resolve(data)
    })
  })
}
 
getFile('./dellyoung.json').then(data=>{
    return getFile(data) 
}).then(data=>{
    return getFile(data)  
}).then(data=>{
    console.log(data)
}).catch(err=>{
    // 统一错误处理
    console.log(err)
})

简直好用了太多。

可以发现,使用promise解决了异步回调的嵌套调用和错误处理的问题。

大家已经知道promise非常重要了,但是如何完全学会promise呢?手撕一遍promise自然就贯通啦,咱们开始撕,在过程中抽丝剥茧。

promise/A+规范
我们现在想写一个promise,但是谁来告诉怎样才算一个合格的promise,不用担心,业界是通过一个规则指标来实现promise的,这就是Promise / A+,还有一篇翻译可供参考【翻译】Promises / A+规范。

接下来就开始逐步实现吧!

同步的promise
先从一个最简单的promise实现开始

构造函数
先实现promise的地基:初始化用的构造函数

class ObjPromise {
    constructor(executor) {
        // promise状态
        this.status = 'pending';
        // resolve回调成功,resolve方法里的参数值
        this.successVal = null;
        // reject回调成功,reject方法里的参数值
        this.failVal = null;
 
        // 定义resolve函数
        const resolve = (successVal) => {
            if (this.status !== 'pending') {
                return;
            }
            this.status = 'resolve';
            this.successVal = successVal;
        };
 
        // 定义reject
        const reject = (failVal) => {
            if (this.status !== 'pending') {
                return;
            }
            this.status = 'reject';
            this.failVal = failVal;
        };
 
        try {
            // 将resolve函数给使用者
            executor(resolve, reject)
        } catch (e) {
            // 执行抛出异常时
            reject(e)
        }
    }
}

咱们先写一个constructor用来初始化promise。

接下来分析一下:

调用ObjPromise传入一个函数命名为executor,executor函数接受两个参数resolve、reject,可以理解为分别代表成功时的调用和失败时的调用。executor函数一般长这样(resolve,reject)=>{...}

status代表当前promise的状态,有三种'pending'、'resolve'、'reject'(注:从状态机考虑的话还有一个额外的初始状态,表示promise还未执行)

successVal和failVal分别代表resolve回调和reject回调携带的参数值

函数resolve:初始化的时候通过作为executor的参数传递给使用者,用来让使用者需要的时候调用,将status状态从'pending'改成'resolve'

函数reject:初始化的时候通过作为executor的参数传递给使用者,将status状态从'pending'改成'reject'

你可能还发现了函数resolve和函数reject 里面都有if (this.status !== 'pending') {return;},这是因为resolve或reject只能调用一次,也就是status状态只能改变一次。

then方法
then方法作用:拿到promise中的resolve或者reject的值。

1.基础版then方法

在class里面放上如下then方法:

then(onResolved, onRejected) {
    switch (this.status) {
        case "resolve":
            onResolved(this.successVal);
            break;
        case "reject":
            onRejected(this.failVal);
            break;
    }
}
来分析一下:

then方法可以传入两个参数,两个参数都是函数,俩函数就像这样(val)=>{...}

当status状态为'resolve'则调用第一个传入的函数,传入的val为successVal

当status状态为'reject'则调用第二个传入的函数,传入的val为failVal

但是then方法还需要支持链式调用的,也就是说可以这样:

new Promise((resolve,reject)=>{
    resolve(1);
}).then((resp)=>{
    console.log(resp); // 1
}).then(()=>{
   ...
})
2.使then方法支持链式调用

其实支持链式核心就是then方法要返回一个新的promise,咱们来改造一下实现支持链式调用。

then(onResolved, onRejected) {
    // 要返回一个promise对象
    let resPromise;
    switch (this.status) {
        case "resolve":
            resPromise = new ObjPromise((resolve, reject) => {
                try{
                    // 传入的第一个函数
                    onResolved(this.successVal);
                    resolve();
                }catch (e) {
                    reject(e);
                }
            });
            break;
        case "reject":
            resPromise = new ObjPromise((resolve, reject) => {
                try{
                    // 传入的第二个函数
                    onRejected(this.failVal);
                    resolve();
                }catch (e) {
                    reject(e);
                }
            });
            break;
    }
    return resPromise;
}
再分析一下:

当status为'resolve'时,将promise成功resolve的结果successVal,传递给第一个方法onResolved(),然后执行onResolved(this.successVal)函数

当status为'reject'时,过程一直,就不多说啦

重点看它们都会把新创建的promise赋值给then方法,执行完后then方法会返回这个新的promise,这样就能实现then的链式调用了

3.使then方法的链式调用可以传参

但是你没有发现一个问题,我then方法内的第一个参数,也就是onResolved()函数,函数内部的返回值应该是要能够传递给下面接着进行链式调用的then方法的,如下所示:

new Promise((resolve,reject)=>{
    resolve(1);
}).then((resp)=>{
    console.log(resp); // 1
    return 2; // <<< 关注这行
}).then((resp)=>{
   console.log(resp); // 2 接受到了参数2
})
这该如何实现呢?

其实很简单:

then(onResolved, onRejected) {
 
    // 定义这个变量保存要返回的promise对象
    let resPromise;
    
    switch (this.status) {
        case "resolve":
            resPromise = new ObjPromise((resolve, reject) => {
                try{
                    // 传入的第一个函数
                    let data = onResolved(this.successVal);
                    resolve(data);
                }catch (e) {
                    reject(e);
                }
            });
            break;
        case "reject":
            resPromise = new ObjPromise((resolve, reject) => {
                try{
                    // 传入的第二个函数
                    let data = onRejected(this.failVal);
                    resolve(data);
                }catch (e) {
                    reject(e);
                }
            });
            break;
    }
    return resPromise;
}
很简单:

先保存函数执行的结果,也就是函数的返回值

然后,将返回值传递给新的用来返回的promise的resolve(),就可以将返回值保存到新的promise的successVal

执行出错的话,当然要将错误传递给新的用来返回的promise的reject(),将错误保存到新的promise的failVal

4.then传入参数处理

再看看这段常见的代码:

new Promise((resolve,reject)=>{
    resolve(1);
}).then((resp)=>{
    console.log(resp); // 1
    return 2; 
}).then((resp)=>{
   console.log(resp); // 2
})
可以看到,then方法的参数可以只传一个,继续来改造:

then(onResolved, onRejected) {
    const isFunction = (fn) => {
        return Object.prototype.toString.call(fn) === "[object Function]"
    };
    onResolved = isFunction(onResolved) ? onResolved : (e) => e;
    onRejected = isFunction(onRejected) ? onRejected : err => {
        throw err
    };
    ······
}
分析一下:

判断传入参数的类型是不是函数

传入类型是函数的话,那没毛病,直接用就行

传入类型不是函数的话,那糟糕啦,咱们得分别用(e) => e和(err)=>{throw err}来替换

到现在promise已经能正常运转啦,代码如下:

class ObjPromise {
    constructor(executor) {
        // promise状态
        this.status = 'pending';
        // resolve回调成功,resolve方法里的参数值
        this.successVal = null;
        // reject回调成功,reject方法里的参数值
        this.failVal = null;
 
        // 定义resolve函数
        const resolve = (successVal) => {
            if (this.status !== 'pending') {
                return;
            }
            this.status = 'resolve';
            this.successVal = successVal;
        };
 
        // 定义reject
        const reject = (failVal) => {
            if (this.status !== 'pending') {
                return;
            }
            this.status = 'reject';
            this.failVal = failVal;
        };
 
        try {
            // 将resolve函数给使用者
            executor(resolve, reject)
        } catch (e) {
            // 执行抛出异常时
            reject(e)
        }
    }
 
    then(onResolved, onRejected) {
        const isFunction = (fn) => {
            return Object.prototype.toString.call(fn) === "[object Function]"
        };
        onResolved = isFunction(onResolved) ? onResolved : (e) => e;
        onRejected = isFunction(onRejected) ? onRejected : err => {
            throw err
        };
 
        // 定义这个变量保存要返回的promise对象
        let resPromise;
 
        switch (this.status) {
            case "resolve":
                resPromise = new ObjPromise((resolve, reject) => {
                    try{
                        // 传入的第一个函数
                        let data = onResolved(this.successVal);
                        resolve(data);
                    }catch (e) {
                        reject(e);
                    }
                });
                break;
            case "reject":
                resPromise = new ObjPromise((resolve, reject) => {
                    try{
                        // 传入的第二个函数
                        let data = onRejected(this.failVal);
                        resolve(data);
                    }catch (e) {
                        reject(e);
                    }
                });
                break;
        }
        return resPromise;
    }
}
你可以在控制台运行下面这个测试代码:

new ObjPromise((resolve,reject)=>{
    resolve(1);
}).then((resp)=>{
    console.log(resp); // 1
    return 2; 
}).then((resp)=>{
   console.log(resp); // 2
})
控制台会依次打印出 1 2。

5.then返回值处理

到现在同步promise代码已经没问题啦,但是还不够,因为Promise/A+规定:then方法可以返回任何值,当然包括Promise对象,而如果是Promise对象,我们就需要将他拆解,直到它不是一个Promise对象,取其中的值。

因为status状态为'resolve'和'reject'时都需要进行这样的处理,所以我们就可以把处理过程封装成一个函数,代码如下:

then(onResolved, onRejected) {
    ···
    let resPromise;
 
    switch (this.status) {
        case "resolve":
            resPromise = new ObjPromise((resolve, reject) => {
                try {
                    // 传入的第一个函数
                    let data = onResolved(this.successVal);
                    this.resolvePromise(data, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            break;
        case "reject":
            resPromise = new ObjPromise((resolve, reject) => {
                try {
                    // 传入的第二个函数
                    let data = onRejected(this.failVal);
                    this.resolvePromise(data, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            break;
    }
    return resPromise;
}
 
// data为返回值
// newResolve为新的promise的resolve方法
// newReject为新的promise的reject方法
resolvePromise(data, newResolve, newReject) {
    // 判断是否是promise,不是直接resolve就行
    if(!(data instanceof ObjPromise)){
        return newResolve(data)
    }
    try {
        let then = data.then;
        const resolveFunction = (newData) => {
            this.resolvePromise(newData, newResolve, newReject);
        };
        const rejectFunction = (err) => {
            newReject(err);
        };
        then.call(data, resolveFunction, rejectFunction)
    } catch (e) {
        // 错误处理
        newReject(e);
    }
}
分析一下:

判断返回值类型,当不是promise时,直接resolve就行

当是promise类型时,用this.resolvePromise(newData, newResolve, newReject)来递归的调用then方法,直到data不为promise,然后resolve结果就行啦

6.解决then返回值循环引用

现在又有问题了:

如果新的promise出现循环引用的话就永远也递归不到头了

看看执行下面这个代码:

let testPromise = new ObjPromise((resolve, reject) => {
    resolve(1);
})
let testPromiseB = testPromise.then((resp) => {
    console.log(resp); // 1
    return testPromiseB;
})
会报错栈溢出。

解决这个问题的方法就是:通过给resolvePromise()方法传递当前新的promise对象,判断当前新的promise对象和函数执行返回值不同就可以了

class ObjPromise {
    constructor(executor) {
        // promise状态
        this.status = 'pending';
        // resolve回调成功,resolve方法里的参数值
        this.successVal = null;
        // reject回调成功,reject方法里的参数值
        this.failVal = null;
 
        // 定义resolve函数
        const resolve = (successVal) => {
            if (this.status !== 'pending') {
                return;
            }
            this.status = 'resolve';
            this.successVal = successVal;
        };
 
        // 定义reject
        const reject = (failVal) => {
            if (this.status !== 'pending') {
                return;
            }
            this.status = 'reject';
            this.failVal = failVal;
        };
 
        try {
            // 将resolve函数给使用者
            executor(resolve, reject)
        } catch (e) {
            // 执行抛出异常时
            reject(e)
        }
    }
 
    resolvePromise(resPromise, data, newResolve, newReject) {
        if (resPromise === data) {
            return newReject(new TypeError('循环引用'))
        }
        if (!(data instanceof ObjPromise)) {
            return newResolve(data)
        }
        try {
            let then = data.then;
            const resolveFunction = (newData) => {
                this.resolvePromise(resPromise, newData, newResolve, newReject);
            };
            const rejectFunction = (err) => {
                newReject(err);
            };
            then.call(data, resolveFunction, rejectFunction)
        } catch (e) {
            // 错误处理
            newReject(e);
        }
    }
 
    then(onResolved, onRejected) {
        const isFunction = (fn) => {
            return Object.prototype.toString.call(fn) === "[object Function]"
        };
        onResolved = isFunction(onResolved) ? onResolved : (e) => e;
        onRejected = isFunction(onRejected) ? onRejected : err => {
            throw err
        };
 
        // 定义这个变量保存要返回的promise对象
        let resPromise;
        switch (this.status) {
            case "resolve":
                resPromise = new ObjPromise((resolve, reject) => {
                    try {
                        // 传入的第一个函数
                        let data = onResolved(this.successVal);
                        this.resolvePromise(resPromise, data, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                break;
            case "reject":
                resPromise = new ObjPromise((resolve, reject) => {
                    try {
                        // 传入的第二个函数
                        let data = onRejected(this.failVal);
                        this.resolvePromise(resPromise, data, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                break;
        }
        return resPromise;
    }
}
可以在控制台中调用如下代码试试啦:

new ObjPromise((resolve, reject) => {
    resolve(1);
}).then((resp) => {
    console.log(resp); // 1
    return 2
}).then((resp) => {
    console.log(resp); // 2
    return new ObjPromise((resolve, reject) => {
        resolve(3)
    })
}).then((resp) => {
    console.log(resp); // 3
});
控制台会一次打印出 1 2 3

异步的promise
现在咱们实现了同步版的promise,但是很多情况下,promise的resolve或reject是被异步调用的,异步调用的话,执行到then()方法时,当前的status状态还是'pending'。这该如何改进代码呢?

思路其实很简单:

设置两个数组,分别存起来then()方法的回调函数onResolved和onRejected

当等到调用了resolve或者reject时,执行对应数组内存入的回调函数即可

另外为了保证执行顺序,等待当前执行栈执行完成,咱们还需要给constructor的resolve和reject函数里面使用setTimeout包裹起来,避免影响当前执行的任务。

根据这个思路来改造一下promise:

class ObjPromise {
    constructor(executor) {
        // promise状态
        this.status = 'pending';
        // resolve回调成功,resolve方法里的参数值
        this.successVal = null;
        // reject回调成功,reject方法里的参数值
        this.failVal = null;
 
        // resolve的回调函数
        this.onResolveCallback = [];
        // reject的回调函数
        this.onRejectCallback = [];
 
        // 定义resolve函数
        const resolve = (successVal) => {
            setTimeout(()=>{
                if (this.status !== 'pending') {
                    return;
                }
                this.status = 'resolve';
                this.successVal = successVal;
 
                //执行所有resolve的回调函数
                this.onResolveCallback.forEach(fn => fn())
            })
        };
 
        // 定义reject
        const reject = (failVal) => {
            setTimeout(()=>{
                if (this.status !== 'pending') {
                    return;
                }
                this.status = 'reject';
                this.failVal = failVal;
 
                //执行所有reject的回调函数
                this.onRejectCallback.forEach(fn => fn())
            })
        };
 
        try {
            // 将resolve函数给使用者
            executor(resolve, reject)
        } catch (e) {
            // 执行抛出异常时
            reject(e)
        }
    }
 
    // data为返回值
    // newResolve为新的promise的resolve方法
    // newReject为新的promise的reject方法
    resolvePromise(resPromise, data, newResolve, newReject) {
        if (resPromise === data) {
            return newReject(new TypeError('循环引用'))
        }
        if (!(data instanceof ObjPromise)) {
            return newResolve(data)
        }
        try {
            let then = data.then;
            const resolveFunction = (newData) => {
                this.resolvePromise(resPromise, newData, newResolve, newReject);
            };
            const rejectFunction = (err) => {
                newReject(err);
            };
            then.call(data, resolveFunction, rejectFunction)
        } catch (e) {
            // 错误处理
            newReject(e);
        }
    }
 
    then(onResolved, onRejected) {
        const isFunction = (fn) => {
            return Object.prototype.toString.call(fn) === "[object Function]"
        };
        onResolved = isFunction(onResolved) ? onResolved : (e) => e;
        onRejected = isFunction(onRejected) ? onRejected : err => {
            throw err
        };
 
        // 定义这个变量保存要返回的promise对象
        let resPromise;
        switch (this.status) {
            case "resolve":
                resPromise = new ObjPromise((resolve, reject) => {
                    try {
                        // 传入的第一个函数
                        let data = onResolved(this.successVal);
                        this.resolvePromise(resPromise, data, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                break;
            case "reject":
                resPromise = new ObjPromise((resolve, reject) => {
                    try {
                        // 传入的第二个函数
                        let data = onRejected(this.failVal);
                        this.resolvePromise(resPromise, data, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                break;
            case "pending":
                resPromise = new ObjPromise((resolve, reject) => {
                    const resolveFunction = () => {
                        try {
                            // 传入的第一个函数
                            let data = onResolved(this.successVal);
                            this.resolvePromise(resPromise, data, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    };
                    const rejectFunction = () => {
                        try {
                            // 传入的第二个函数
                            let data = onRejected(this.failVal);
                            this.resolvePromise(resPromise, data, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    };
                    this.onResolveCallback.push(resolveFunction);
                    this.onRejectCallback.push(rejectFunction);
                });
                break;
        }
        return resPromise;
    }
}
可以用下面代码测试一下:

new ObjPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 100)
}).then((resp) => {
    console.log(resp); // 1
    return 2
}).then((resp) => {
    console.log(resp); // 2
    return new ObjPromise((resolve, reject) => {
        resolve(3)
    })
}).then((resp) => {
    console.log(resp); // 3
});
我们现在已经基本完成了Promise的then方法啦。

完善promise
到现在已经完成了promise最核心的两个方法:constructor方法和then方法。不过Promise/A+还规定了一些其他的方法,咱们继续来完成。

catch方法
catch()方法就是可以通过回调函数拿到reject的值,这个好办,其实then方法已经实现了,转接一下then方法就行了:

catch(onRejected) {
    return this.then(null, onRejected)
}
这样就实现了catch()方法

Promise.resolve()/reject()方法
大家肯定都见过Promise.resolve()或者Promise.resolve()用法。其实作用就是返回一个新的promise,并且内部调用resolve或者reject。

ObjPromise.resolve = (val) => {
    return new ObjPromise((resolve, reject) => {
        resolve(val)
    })
};
 
ObjPromise.reject = (val) => {
    return new ObjPromise((resolve, reject) => {
        reject(val)
    })
};
通过这两种方法,咱们可以将现有的数据很方便的转换成promise对象

all方法
all方法也是很常用的方法,它可以传入promise数组,当全部resolve或者有一个reject时,执行结束,当然返回的也是promise对象,来实现一下。

ObjPromise.all = (arrPromise) => {
    return new ObjPromise((resolve, reject) => {
        // 传入类型必须为数组
        if(Array.isArray(arrPromise)){
            return reject(new TypeError("传入类型必须为数组"))
        }
        // resp 保存每个promise的执行结果
        let resp = new Array(arrPromise.length);
        // 保存执行完成的promise数量
        let doneNum = 0;
        for (let i = 0; arrPromise.length > i; i++) {
            // 将当前promise
            let nowPromise = arrPromise[i];
            if (!(nowPromise instanceof ObjPromise)) {
                return reject(new TypeError("类型错误"))
            }
            // 将当前promise的执行结果存入到then中
            nowPromise.then((item) => {
                resp[i] = item;
                doneNum++;
                if(doneNum === arrPromise.length){
                    resolve(resp);
                }
            }, reject)
        }
    })
};
来分析一下:

传入promise数组,返回一个新的promsie对象

resp用来保存所有promise的执行结果

用instanceof来判断是否是promise类型

通过调用每个promise的then方法拿到返回值,并且要传入reject方法

用doneNum来保存执行完成的promise数量,全部执行完后,通过resolve传递执行结果resp,并且将当前promise状态改为'resolve',后续就可以通过then方法取值

race方法
race方法也偶尔会用到,它可以传入promise数组,当哪个promise执行完,则race就直接执行完,咱们来实现一下:

ObjPromise.race = (arrPromise) => {
    return new Promise((resolve, reject) => {
        for (let i = 0; arrPromise.length > i; i++) {
            // 将当前promise
            let nowPromise = arrPromise[i];
            if (!(nowPromise instanceof ObjPromise)) {
                return reject(new TypeError("类型错误"))
            };
            nowPromise.then(resolve, reject);
        }
    })
};
来分析一下:

传入promise数组,返回一个新的promsie对象

用instance来判断是否是promise类型

调用每个promise的then方法,并传递resolve、reject方法,哪个先执行完就直接结束了,后续就可以通过then方法取值

OK,到现在已经实现了一个自己的promise对象!
————————————————
版权声明:本文为CSDN博主「winty~~」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/LuckyWinty/article/details/107970255

猜你喜欢

转载自blog.csdn.net/gcyaozuodashen/article/details/128100013