一步一步手撕Promise
手撕Promise已经是一个非常经典的面试题了,没办法,面试官就喜欢和你吹逼造火箭。学完会:
- 知道promise的执行流程
- promise如何实现链式调用的
- 可以自己手写一个自己的promise
- 面试造火箭
建议多看几遍,理解流程,然后一步一步敲一下代码。
关于Promise
在手撕Promise之前,我们要先观察一下Promise对象,我们知道promise有3个状态:
- pending
- fulfilled
- rejected
通过调用resolve()就会将状态从pending转为fulfilled;调用reject()就会将状态从pending转为rejected(),且状态的转换是不可逆的。
var p1 = new Promise(function (resolve, reject) {
console.log(resolve, reject);
});
console.log(p1);
var p2 = new Promise(function (resolve, reject) {
resolve("成功");
});
console.log(p2);
var p3 = new Promise(function (resolve, reject) {
reject("失败");
});
console.log(p3);
复制代码
我们先观察一下,各个状态的返回结果。
也可以发现,resolve和reject是2个函数。
总体结构
function MyPromise(fn) {
//promise的初始状态为pending,可变成fulfilled或rejected其中之⼀
this.promiseState = "pending";
this.promiseValue = undefined;
var _this = this;
var resolve = function (value) {
if (_this.promiseState == "pending") {
_this.promiseState = "fulfilled";
_this.promiseValue = value;
}
};
var reject = function (value) {
if (_this.promiseState == "pending") {
_this.promiseState = "rejected";
_this.promiseValue = value;
}
};
if (fn) {
fn(resolve, reject);
} else {
throw "Init Error,Please use a function to init MyPromise!";
}
}
MyPromise.prototype.then = function (callback) {};
MyPromise.prototype.catch = function (callback) {};
复制代码
我们知道Promise是一个构造函数,需要传入一个回调函数来进行实例化。否则就会报错。
所以上述代码中,我们定义了一个fn的参数,这个是将来需要传入的函数。如果这个函数不存在,我们就抛出一个异常。
其次,按照对象特性,Promise初始化的时候回调函数是同步执行的,并且该回调函数有2个参数,分别是resolve和reject,resolve和reject是两个函数。因此,我们判断fn函数存在时,就直接调用这个函数,并且传入resolve和reject。
但是resolve和reject哪里来呢?
于是我们就通过var定义了这2个函数,这2个函数的调用会更改Promise内部的状态,并且这2个函数有一个参数,参数传进去的值最后会成为promise实例的[[promiseResult]]
的值。
因为这个是一个构造函数,所以我们定义了2个属性:promiseState
和promiseValue
,new的时候会成为实例的属性。promise初始状态为pending,值为undefined。
我们将this赋值给一个_this的变量,因为我们需要拿到一个指向promise实例对象的this;同时给resolve和reject定义一个行参,我们在调用时才会传入实参。
我们知道,promise的状态是不可逆的,也就是说,它只能从pending转到fulfilled或者rejected。
因此,我们在resolve和reject内部需要做一个判断,只有promiseState
状态为pending
的时候,我们才可以转为fulfilled或者rejected,并且将参数的value赋值给promise的value即promiseValue。
我们观察一下Promise构造函数,发现它的catch和then方法都在原型对象上,于是我们给自己的Promise定义了自己的then方法和catch方法。
目前,resolve和reject的作用就是更改promise实例的内部value值和状态。
实现then方法
总体结构写好了我们先来看看我们运行后有什么效果。
var p = new MyPromise(function (resolve, reject) {
resolve(123);
});
console.log(p);
p.then(function (res) {
console.log(res);
});
复制代码
resolve之后我们看了一下结果,发现状态和值都改变成功了,然后调用了.then方法,因为我们什么都没写,所以不会有任何的反应。
思路
我们先来想一想,之前我们在讲promise执行流程的时候有说过,promise的回调是同步执行的。
.then()方法是一个微任务,即异步任务。并且.then()中可以传入2个回调函数,第一个是成功的回调,第二个是失败的回调。我们先暂时忽略第二个。
.then()的执行时机是在resolve执行之后,并且需要获取到resolve的实参。
所以我们可以给MyPromise定义一个thenCallback
的属性,默认为undefined,如果then执行了,这个属性将会被赋值为一个函数,这个函数有一个形参用来接收resolve()的value值(即实参),并且会将value作为.then接受到的回调函数的实参去执行。具体看如下代码:
实现
function MyPromise(fn) {
this.promiseState = "pending";
this.promiseValue = undefined;
var _this = this;
// 定义了.thenCallback的属性,一开始时是undefined
this.thenCallback = undefined;
var resolve = function (value) { // value是一个形参
if (_this.promiseState == "pending") {
_this.promiseState = "fulfilled";
_this.promiseValue = value;
//异步的执⾏then函数中注册的回调函数
setTimeout(function () {
if (_this.thenCallback) {
// 将value值给thenCallback,thenCallback会给.then()括号内的回调函数
_this.thenCallback(value);
}
});
}
};
var reject = function (value) {
if (_this.promiseState == "pending") {
_this.promiseState = "rejected";
_this.promiseValue = value;
}
};
if (fn) {
fn(resolve, reject);
} else {
throw "Init Error,Please use a function to init MyPromise!";
}
}
MyPromise.prototype.then = function (callback) {
//then第⼀次执⾏时注册回调函数到当前的Promise对象,thenCallback接受到一个函数,这个函数有一个形参,将会是resolve()的值,这个形参会给callback进行调用
this.thenCallback = function (value) {
callback(value);
};
};
MyPromise.prototype.catch = function (callback) {};
var p = new MyPromise(function (resolve, reject) {
resolve(123);
});
console.log(p);
p.then(function (res) {
console.log(res);
});
复制代码
我们可以看到.then正确执行了一次。
流程详解
上述代码中:
我们将Promise实例的信息都定义在了MyPromise构造函数内部,比如:状态、值等等。初始状态时pendding,值是undefined。
MyPromise原型中有.then的方法,上述代码的.then方法就只负责将.then(callback)的callback回调函数赋值给new MyPromise出的实例的thenCallback属性。
而MyPromise内部的resolve执行时,有一个setTimeout定时器,他会把它的回调函数挂起,等待同步代码执行完毕后再去执行它的回调。
所以实际上的执行顺序是:
-
resolve()执行,把promiseState变成了fulfilled,promiseValue变成了123。
-
然后执行到定时器,定时器的回调函数挂起。
-
再console.log(p)
-
执行.then函数,传入了
function(res)...
回调函数,这时候会将thenCallback属性从undefined变成了传入的function(res)...
回调函数。 -
这时候主程序的代码执行完了,开始执行挂起的异步代码,然后将123传给thenCallback,实际上就是传给
function(res)...
-
最后打印123
then的异步链式调用
目前我们已经实现了单个.then的调用了,但是原先的Promise可以通过.then来实现链式调用,而我们再次使用.then的时候,直接就报错了。
首先我们先观察一下原先的Promise调用.then之后返回了什么?
var p1 = new Promise(function (resolve, reject) {
resolve(123);
}).then((res) => {
console.log(res);
});
console.log(p1, 222);
复制代码
我们可以发现,.then执行后,默认返回了Promise的实例。
因此我们可以大胆推测,.then执行的时候实际上是返回了MyPromise的实例,毕竟.then方法也在MyPromise的原型上,所以实现了链式调用。
.then的回调函数执行时,会return一个值,会成为下一个.then的回调函数的实参。这个值可能是一个MyPromise,也可能是常规一个值。如果是一个MyPromise实例,则需要它resolve的值。且,整个.then调用会返回一个MyPromise的新实例。
实现
function MyPromise(fn) {
//promise的初始状态为pending,可变成fulfilled或rejected其中之⼀
this.promiseState = "pending";
this.promiseValue = undefined;
var _this = this;
// 定义了.thenCallback的属性,用来接受.then执行时的回调函数
this.thenCallback = undefined;
var resolve = function (value) {
// 这里判断的是当前的promise的状态
if (_this.promiseState == "pending") {
_this.promiseState = "fulfilled";
_this.promiseValue = value;
//当传⼊的数据是自定义的Promise
if (value instanceof MyPromise) {
// 直接调用.then拿到resolve的值,并把值给thenCallback让新的promise实例拿到
value.then(function (res) {
_this.thenCallback(res);
});
} else {
// 如果传入的数据是普通变量
setTimeout(function () {
if (_this.thenCallback) {
_this.thenCallback(value);
}
});
}
}
};
var reject = function (value) {
if (_this.promiseState == "pending") {
_this.promiseState = "rejected";
_this.promiseValue = value;
}
};
if (fn) {
fn(resolve, reject);
} else {
throw "Init Error,Please use a function to init MyPromise!";
}
}
MyPromise.prototype.then = function (callback) {
//then第⼀次执⾏时注册回调函数到当前的Promise对象
var _this = this;
// 整个.then执行会返回一个新的MyPromise实例
return new MyPromise(function (resolve, reject) {
// 通过下面方式可以拿到上一个promise中resolve的值,因为_this.thenCallback指向上一个promise的thenCallback。
_this.thenCallback = function (value) {
var callbackRes = callback(value);
// 执行resolve,拿到是.then回调函数执行后返回的值,同时会将新的promise状态变成fuifilled,值变成callbackRes
resolve(callbackRes);
};
});
};
MyPromise.prototype.catch = function (callback) {};
复制代码
然后我们先执行一下:
var p = new MyPromise(function (resolve, reject) {
resolve(123);
})
.then(function (res) {
console.log(res, "===1");
return 456;
})
.then(function (res1) {
console.log(res1, "===2");
return 666;
});
console.log(p, "===3");
const p1 = p.then((res) => {
return new MyPromise(function (resolve, reject) {
resolve(res + 1);
});
});
console.log(p1, "===4");
const p2 = p1.then((res) => {
return "chenjh";
});
console.log(p2, "===5");
var p3 = new MyPromise(function (resolve, reject) {
resolve(
new MyPromise(function (resolve, reject) {
resolve("test");
})
);
});
console.log(p3, "===6");
复制代码
代码解析
MyPromise.prototype.then = function (callback) {
//then第⼀次执⾏时注册回调函数到当前的Promise对象
var _this = this;
return new MyPromise(function (resolve, reject) {
_this.thenCallback = function (value) {
var callbackRes = callback(value);
resolve(callbackRes);
};
});
};
复制代码
我们知道实现链式调用需要返回同一个构造函数new出来的实例,因此我们直接return一个新的MyPromise实例。
我们之前说过要把callback传给调用.then()的那个promise实例,而new出来的promise内部的this指向了新的promise实例,因此我们需要将外层的this给一个变量_this,新的promise实例访问外层的promise实例的thenCallback属性,并将那个属性赋值成一个带形参的函数,在这个函数内部调用callback函数。
而下一个.then方法需要访问当前.then回调函数return的值,因此我们定义了一个callbackRes接受回调函数执行后return的值。并且将这个值传如resolve,可以让下面代码取到。就相当于每次都是new Promise((resolve,reject)=>{resolve(xxxx)}).then()...
var resolve = function (value) {
if (_this.promiseState == "pending") {
_this.promiseState = "fulfilled";
_this.promiseValue = value;
//当传⼊的数据是自定义的Promise
if (value instanceof MyPromise) {
value.then(function (res) {
_this.promiseValue = res;
if (_this.thenCallback) {
_this.thenCallback(res);
}
});
} else {
// 如果传入的数据是普通变量
setTimeout(function () {
if (_this.thenCallback) {
_this.thenCallback(value);
}
});
}
}
};
复制代码
上述代码中,我们需要判断resolve中的参数value值是否是一个MyPromise,如果是的话我们直接调用.then就可以拿到resolve中的参数value值,再去调用_this.thenCallback。
注:上面这一块可能会有点绕,建议多看几遍代码,每一次return一个new promise其实都是一个新的promise实例!!!
实现catch的流程处理
catch的流程类似,参考上面的then的处理即可。
function MyPromise(fn) {
//promise的初始状态为pending,可变成fulfilled或rejected其中之⼀
this.promiseState = "pending";
this.promiseValue = undefined;
var _this = this;
// 定义了.thenCallback的属性,用来接受.then执行时的回调函数
this.thenCallback = undefined;
// 定义了.catchCallback的属性.catch执行时的回调函数
this.catchCallback = undefined;
var resolve = function (value) {
if (_this.promiseState == "pending") {
_this.promiseState = "fulfilled";
_this.promiseValue = value;
//当传⼊的数据是自定义的Promise
if (value instanceof MyPromise) {
value.then(function (res) {
_this.promiseValue = res;
if (_this.thenCallback) {
_this.thenCallback(res);
}
});
} else {
// 如果传入的数据是普通变量
setTimeout(function () {
if (_this.thenCallback) {
_this.thenCallback(value);
}
});
}
}
};
var reject = function (err) {
if (_this.promiseState == "pending") {
_this.promiseValue = err;
_this.promiseState = "rejected";
setTimeout(function () {
if (_this.catchCallback) {
_this.catchCallback(err);
}
});
}
};
if (fn) {
fn(resolve, reject);
} else {
throw "Init Error,Please use a function to init MyPromise!";
}
}
MyPromise.prototype.then = function (callback) {
//then第⼀次执⾏时注册回调函数到当前的Promise对象
var _this = this;
return new MyPromise(function (resolve, reject) {
_this.thenCallback = function (value) {
var callbackRes = callback(value);
resolve(callbackRes);
};
});
};
MyPromise.prototype.catch = function (callback) {
var _this = this;
return new MyPromise(function (resolve, reject) {
_this.catchCallback = function (errValue) {
var callbackRes = callback(errValue);
resolve(callbackRes);
};
});
};
复制代码
我们尝试调用一下:
var p = new MyPromise(function (resolve, reject) {
reject("err");
});
p.then(function (res) {
console.log(res);
return 111;
})
.then(function (res) {
console.log(res);
return 111;
})
.then(function (res) {
console.log(res);
return 111;
})
.catch(function (err) {
console.log(err);
});
console.log(p);
复制代码
const p = new Promise((resolve, reject) => {
reject("123");
});
console.log(p);
const p1 = p.then((res) => {
console.log(res);
return 1;
});
console.log(p1);
const p2 = p1.catch((err) => {
console.log(err);
});
console.log(p2);
复制代码
发现虽然得到了3个promise实例,但是是存在问题的。
实现跨对象执行reject
正常的Promise流程中,如果遇到了reject执行,就会去执行.catch的回调。
而按照我们之前的代码流程,Promise对象会⾃动变更状态为rejected并且catch的回调函数⽆法注册,所以Promise的流程就断了。这个时候需要追加判断代码让Promise在rejected时如果没有catchCallback再去检测是否存在thenCallback。
var reject = function (err) {
if (_this.promiseState == "pending") {
_this.promiseValue = err;
_this.promiseState = "rejected";
setTimeout(function () {
if (_this.catchCallback) {
_this.catchCallback(err);
} else if (_this.thenCallback) {
_this.thenCallback(err);
} else {
throw "this Promise was reject,but can not found catch!";
}
});
}
};
if (fn) {
fn(resolve, reject);
} else {
throw "Init Error,Please use a function to init MyPromise!";
}
复制代码
还需要把prototype.then的改成如下:
MyPromise.prototype.then = function (callback) {
//then第⼀次执⾏时注册回调函数到当前的Promise对象
var _this = this;
return new MyPromise(function (resolve, reject) {
_this.thenCallback = function (value) {
//判断如果进⼊该回调时Promise的状态为rejected那么就直接触发后续Promise的catchCallback
//直到找到catch
if (_this.promiseState == "rejected") {
reject(value);
} else {
var callbackRes = callback(value);
resolve(callbackRes);
}
};
});
};
// 修改之后执行
var p = new MyPromise(function (resolve, reject) {
reject("err");
});
p.then(function (res) {
console.log(res);
return 111;
})
.then(function (res) {
console.log(res);
return 111;
})
.then(function (res) {
console.log(res);
return 111;
})
.catch(function (err) {
console.log(err);
});
console.log(p);
复制代码
实现链式调用的中断
首先给原型增加reject方法
MyPromise.reject = function(value){
return new MyPromise(function(resolve,reject){
reject(value)
})
}
复制代码
再去修改then的方法
MyPromise.prototype.then = function (callback) {
//then第⼀次执⾏时注册回调函数到当前的Promise对象
var _this = this;
return new MyPromise(function (resolve, reject) {
_this.thenCallback = function (value) {
if (_this.promiseState == "rejected") {
reject(value);
} else {
var callbackRes = callback(value);
if (callbackRes instanceof MyPromise) {
if (callbackRes.promiseState == "rejected") {
callbackRes.catch(function (errValue) {
reject(errValue);
});
}
} else {
resolve(callbackRes);
}
}
};
});
};
复制代码
var p = new MyPromise(function (resolve, reject) {
resolve(123);
});
console.log(p);
const p1 = p
.then(function (res) {
console.log("then1执⾏");
return 456;
})
.then(function (res) {
console.log("then2执⾏");
return MyPromise.reject("中断了");
})
.then(function (res) {
console.log("then3执⾏");
return 789;
})
.then(function (res) {
console.log("then4执⾏");
return 666;
})
.catch(function (err) {
console.log("catch执⾏");
console.log(err);
});
console.log(p1);
复制代码