本文将通过一步步详细的分析,带你从0开始,写出promise源码。
promise类的核心逻辑实现
首先,以终为始,我们先来看看promise最终实现的样子
let promise = new Promise((resolve, reject) => {
resolve('成功');
reject('失败');
});
promise.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
);
通过,代码我们可以发现,promise使用了new关键字。构造函数里面传入了一个函数,这个函数带有两个参数resolve和reject。
基于以上分析,我们可以写出以下代码
class MyPromise { //将类命名为MyPromise类
constructor(executor){ //构造器中传入一个函数,executor,这个函数立即执行
executor(resolve,reject)
}
}
同时我们知道,promise中的resolve和reject是用来实现状态状态转换的。resolve将等待状态转换成为成功状态,reject将等待转换为失败状态。也就是说promise中将有一个状态量,通过promise中的类方法,将这个状态量实现从等待->成功/或者失败的转换。于是改进代码:
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { //类的命名规范
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING //状态变量
resolve(){ //成功时候的状态转换
this.status = FULFILLED
}
reject(){ //失败时候的状态转换
this.status = REJECT
}
}
同时,我们知道,状态的转换只能由等待转换到成功或者失败。也就是说,初始状态不是等待时,我们是不会执行状态转换的。上面代码优化成为
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { //类的命名规范
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING //状态变量
resolve(){ //成功时候的状态转换
if(this.status !== PENDING) return //不是等待状态不执行转换
this.status = FULFILLED
}
reject(){ //失败时候的状态转换
if(this.status !== PENDING) return //不是等待状态不执行转换
this.status = REJECT
}
}
在promise的使用中,使用then来处理回调,即promise.then().这里传入了两个函数,分别表示成功的回调和失败的回调。成功的回调函数有形参value,失败的函数有形参reason,完善promise类如下
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { //类的命名规范
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING //状态变量
resolve = ()=>{ //成功时候的状态转换
if(this.status !== PENDING) return
this.status = FULFILLED
}
reject= ()=>{ //失败时候的状态转换
if(this.status !== PENDING) return
this.status = REJECT
}
then(successCallback,failCallback){ //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
if(this.status === FULFILLED){
successCallback(value) //status为FULFILLED的回调
}else if(this.status === REJECT){
failCallback(reason) // status为REJECT的回调
}
}
}
同时,我们知道,成功回调successCallback的value和failCallback里面的reason就是在执行状态转换的时候传入的参数。我们将value和reason定义为一个变量。
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECT = 'reject'
class MyPromise { //类的命名规范
constructor(executor){
executor(this.resolve,this.reject)
}
status = PENDING //状态变量
value = undefined //成功时候的值
reason = undefined //失败时的原因
resolve=value=>{ //成功时候的状态转换
if(this.status !== PENDING) return
this.status = FULFILLED
this.value = value //成功时候保存成功量
}
reject=reason=>{ //失败时候的状态转换
if(this.status !== PENDING) return
this.status = REJECT
this.reason = reason //失败时后保存失败原因
}
then(successCallback,failCallback){ //then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
if(this.status === FULFILLED){
successCallback(this.value) //status为FULFILLED的回调
}else if(this.status === REJECT){
failCallback(this.reason) // status为REJECT的回调
}
}
}
module.exports = MyPromise;
以上,promise的核心逻辑就实现完了,现在我们来测试一下代码
const MyPromise = require("./myPromise"); //导入类
let promise = new MyPromise((resolve, reject) => {
resolve("成功"); //实现成功的函数
// reject("失败");
});
promise.then(
(value) => {
console.log(value);
},
(reason) => {
console.log(reason);
}
);
至此,我们整个promise核心逻辑实现完毕。
promise类中加入异步逻辑
上面我们实现的MyPromise类并没有考虑异步代码的实现。我们在测试代码中间加入异步逻辑。
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) => {
// resolve("成功");
// reject("失败");
setTimeout(() => { //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
resolve("成功");
}, 2000);
});
promise.then(
(value) => {
console.log(value);
},
(reason) => {
console.log(reason);
}
);
因为我们在then方法里面没有实现PENDING的逻辑,我们现在实现。由于等待状态时,还没有实现状态转换,我们先将两个回调函数存起来。然后在状态转换的函数resolve和reject里调用,这样就可以保证回调函数被触发。
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
//类的命名规范
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; //状态变量
value = undefined; //成功时候的值
reason = undefined; //失败时的原因
successCallback = undefined; //成功回调函数
failCallback = undefined; //失败回调函数
resolve = (value) => {
//成功时候的状态转换
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
this.successCallback && this.successCallback(value); //先判断成功回调函数存不存在,存在则调用
};
reject = (reason) => {
//失败时候的状态转换
if (this.status !== PENDING) return;
this.status = REJECT;
this.reason = reason;
this.failCallback && this.failCallback(reason); //先判断失败回调函数存不存在,存在则调用
};
then(successCallback, failCallback) {
//then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
if (this.status === FULFILLED) {
successCallback(this.value); //status为FULFILLED的回调
} else if (this.status === REJECT) {
failCallback(this.reason); // status为REJECT的回调
} else {
//还是等待状态,我们先将连个回调函数存起来
this.successCallback = successCallback;
this.failCallback = failCallback;
}
}
}
module.exports = MyPromise;
再次使用上面的异步测试代码,测试发现完美执行,至此我们就实现了在promise类中加入异步逻辑。
实现then的多次调用
我们知道,promise的then方法是可以多次调用,但是我们上面的代码中的回调函数只能储存一个函数,所以要进行一些改造让其实现then的多次调用
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
//类的命名规范
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; //状态变量
value = undefined; //成功时候的值
reason = undefined; //失败时的原因
successCallback = []; //成功回调函数
failCallback = []; //失败回调函数
resolve = (value) => {
//成功时候的状态转换
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length) //循环判断成功数组里面是否还有回调函数还没有执行,有则出队列并执行函数
this.successCallback.shift()(this.value);
};
reject = (reason) => {
//失败时候的状态转换
if (this.status !== PENDING) return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);//循环判断是失败数组里面是否还有回调函数还没有执行,有则出队列并执行函数
};
then(successCallback, failCallback) {
//then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
if (this.status === FULFILLED) {
successCallback(this.value); //status为FULFILLED的回调
} else if (this.status === REJECT) {
failCallback(this.reason); // status为REJECT的回调
} else {
//还是等待状态,我们先将连个回调函数存起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
}
}
module.exports = MyPromise;
至此,我们实现了then的多次调用。
then的链式调用
我们知道promise是可以是西安链式调用的,但是我们上面的代码并没有实现这个功能。而我们修改一下测试代码
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) => {
// resolve("成功");
// reject("失败");
setTimeout(() => {
//这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
resolve("成功");
}, 2000);
});
promise //promise链式调用
.then(
(value) => {
console.log(value);
},
(reason) => {
console.log(reason);
}
)
.then((value) => {
console.log(value);
});
我们观察链式调用发现,要实现链式调用首先要在每一个then中返回一个promise对象因为只有promise对象才能调用then方法,同时我们还要实现链式then之间的传值。接下类,我们就动手是实现一下then的链式调用
我们先写好测试代码
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) => {
resolve("成功");
// reject("失败");
// setTimeout(() => {
// //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
// resolve("hahha1");
// }, 2000);
});
promise
.then((value) => {
// console.log(123);
console.log(value);
return 100;
})
.then((value) => {
console.log(value);
});
然后改造代码如下
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
//类的命名规范
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; //状态变量
value = undefined; //成功时候的值
reason = undefined; //失败时的原因
successCallback = []; //成功回调函数
failCallback = []; //失败回调函数
resolve = (value) => {
//成功时候的状态转换
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length)
this.successCallback.shift()(this.value);
};
reject = (reason) => {
//失败时候的状态转换
if (this.status !== PENDING) return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);
};
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => { //定义一个新的promise对象
//then传入两个回调函数,一个成功的回调函数,一个失败的回调函数,因为传给构造函数的函数是理及执行的所以可以将其他语句放进去
if (this.status === FULFILLED) {
let x = successCallback(this.value); //status为FULFILLED的回调
resolve(x); //在这里实现将上一个promise对象的返回值传递給下一个promise
} else if (this.status === REJECT) {
failCallback(this.reason); // status为REJECT的回调
} else {
//还是等待状态,我们先将连个回调函数存起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
});
return promise2; //返回这个promise对象
}
}
module.exports = MyPromise;
现在我们就实现了,then方法的链式调用。但是有的时候我们的then除了返回值还会返回promise对象 。也就是说我们现在要判断返回的x是普通值还是promise对象如果是普通值,直接调用resolve,如果是promise对象,查看promise对象返回的结果,根据结果决定调用返回的是resolve还是reject。
我们先写好测试代码
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) => {
resolve("成功");
// reject("失败");
// setTimeout(() => {
// //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
// resolve("hahha1");
// }, 2000);
});
promise
.then((value) => {
// console.log(123);
console.log(value);
return other();
})
.then((value) => {
console.log(value);
});
function other(){
return new Promise((resolve,reject)=>{
resolve('成功123')
})
}
接下来改进我们原来的代码如下
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
//类的命名规范
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; //状态变量
value = undefined; //成功时候的值
reason = undefined; //失败时的原因
successCallback = []; //成功回调函数
failCallback = []; //失败回调函数
resolve = (value) => {
//成功时候的状态转换
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length)
this.successCallback.shift()(this.value);
};
reject = (reason) => {
//失败时候的状态转换
if (this.status !== PENDING) return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);
};
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
//then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
if (this.status === FULFILLED) {
let x = successCallback(this.value); //status为FULFILLED的回调
resolvePromise(x, resolve, reject); //调用resolvePromise函数
} else if (this.status === REJECT) {
failCallback(this.reason); // status为REJECT的回调
} else {
//还是等待状态,我们先将连个回调函数存起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
});
return promise2;
}
}
//判断一个值x是不是promise对象,如果是执行promise对象,再调用x的then方法resolve,如果不是执行reject
function resolvePromise(x, resolve, reject) {
if (x instanceof Promise) {
//x是promise对象
x.then(resolve, reject);
} else {
//x是正常值
resolve(x);
}
}
module.exports = MyPromise;
现在我们就可以实现对返回值为promise对象的处理了。但是这里有一个问题就是假如在then方法中返回了这个promise对象本身,上面的代码就会出现循环调用的情况,这是不允许出现的。在系统的promise中,这种情况会出现报错,但是不会出现错误。
循环调用的情况:
const MyPromise = require("./myPromise");
let promise = new MyPromise((resolve, reject) => {
resolve("成功");
// reject("失败");
// setTimeout(() => {
// //这里再两秒后才执行状态转换,代码先执行了then方法,但是此时的状态还是PANDING,但是then中并没有对PENDING状态处理的逻辑,我们给他添加上。
// resolve("hahha1");
// }, 2000);
});
let p1 = promise.then((value) => { //这里发生了promise的循环调用
// console.log(123);
console.log(value);
return p1; //返回调用的promise本身
});
p1.then(
(value) => {
console.log(value);
},
(reason) => {
console.log(reason);
}
);
function other() {
return new Promise((resolve, reject) => {
resolve("成功l");
});
}
解决这种情况其实也不难,我们只需要在resolvePromise中先判断这个x是不是和调用它的promise是相同的。如果是就不往下执行代码。改进之后的代码如下
//这里将三个状态(等待,成功,失败)定义为常量,方便后面的复用和代码的提示
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";
class MyPromise {
//类的命名规范
constructor(executor) {
executor(this.resolve, this.reject);
}
status = PENDING; //状态变量
value = undefined; //成功时候的值
reason = undefined; //失败时的原因
successCallback = []; //成功回调函数
failCallback = []; //失败回调函数
resolve = (value) => {
//成功时候的状态转换
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
// this.successCallback && this.successCallback(value);
// console.log(this.successCallback);
while (this.successCallback.length)
this.successCallback.shift()(this.value);
};
reject = (reason) => {
//失败时候的状态转换
if (this.status !== PENDING) return;
this.status = REJECT;
this.reason = reason;
// this.failCallback && this.failCallback(reason);
while (this.failCallback.length) this.failCallback.shift()(this.reason);
// while (this.failCallback.length) console.log(this.failCallback);
};
then(successCallback, failCallback) {
let promise2 = new MyPromise((resolve, reject) => {
//then传入两个回调函数,一个成功的回调函数,一个失败的回调函数
if (this.status === FULFILLED) {
setTimeout(() => { //这里有一个小细节,使用在resolvePromise的时候使用了实参promise2,但是promise2还没有完成new,所以是不存在的,我们可以使用setTimeout方法是让语句进入事件队列最后再来执行,这是就可以获取到promise2了
let x = successCallback(this.value); //status为FULFILLED的回调
resolvePromise(promise2, x, resolve, reject);
}, 0);
} else if (this.status === REJECT) {
failCallback(this.reason); // status为REJECT的回调
} else {
//还是等待状态,我们先将连个回调函数存起来
this.successCallback.push(successCallback);
this.failCallback.push(failCallback);
}
});
return promise2;
}
}
//判断一个值x是不是promise对象,如果是执行resolve,如果不是执行reject
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject( //如果是循环调用的话,会报一个类型错误
new TypeError("Chaining cycle detected for promise #<Promise>")
);
}
if (x instanceof Promise) {
//x是promise对象
x.then(resolve, reject);
} else {
//x是正常值
resolve(x);
}
}
module.exports = MyPromise;
完善代码增加健壮性
执行器错误
我们先改造一下
我的一些感悟就是:
以终为始分析问题,由promise的使用反推出实现。再使用金字塔原理步步细化。
人类从掌握使用工具开始,就在加速的进步着,掌握工具是实现的一个好方法