持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
--前言--
Promise在面试里一直是一个至关重要的一道坎,而它的实现原理在面试里出现的概率更是无限趋近于百分之一百,可想而知Promise的重要性。
在面试里,经常会要求我们手写promise.then或者promise.catch等等,但是要知道的是它们的实现原理都类似,所以这里咱们主要以讲怎么手写promise.then为主。(当然,不是A+规范)
第一步:定义一个Promise构造函数
我们都知道,Promise 是一个构造函数,那么我们第一步当然是要来定义一个这样的构造函数;
不过为了与原本的Promise构造函数进行区分,咱们这里定义的构造函数叫MyPromise构造函数。
//这里我们在MyPromise构造函数外面套了一层自执行函数,目的是习惯不把这样的构造函数的作用域暴露在太外层,所以就算不加上这外面的自执行函数也一样。
(function(window){
function MyPromise(){
}
window.MyPromise = MyPromise //将MyPromise挂载到window上(浏览器可执行,node引擎不支持)
})(window)
那么定义了一个MyPromise构造函数之后咱们就要考虑 .then 该如何实现了;
要让MyPromise构造函数定义出来的实例对象可以使用 .then ;
那么我们就要把 .then 定义到 MyPromise 的原型(prototype)上。
代码增添过如下:
(function(window){
MyPromise.prototype.then = function(){ //将.then挂载到原型上
}
MyPromise.prototype.catch = function(){
}
function MyPromise(){
}
window.MyPromise = MyPromise
})(window)
那么这个基本的架构就已经出来了。
第二步:构造 .then 的回调
.then 的回调? .then 有哪些回调?
诺,这就是原本官方的的 Promise 函数里 .then 可以执行的回调:
let p = new Promise((resolve,reject)=>{
resolve('ok');
reject('no');
})
p.then(
(res) =>{
console.log('res',res);
},
(error) =>{
console.log('error',error);
}
)
如果 Promise 的实例对象里用的是 resolve 函数,那么它的 .then 里就会走 res 的那个回调函数;
如果走的是 reject 函数,那么它的 .then 里将会走的就是 error 那个回调函数了。(效果等同于catch)
那么观望完官方的回调,我们也继续开始完善我们的 MyPromise 吧!
既然官方有给 .then 放置两个回调,那么我们就在 .then 的函数上加两个形参 onResolve 和 onReject。
同时顺便给 .catch 加上一个 onReject 回调.
(function(window){
MyPromise.prototype.then = function(onResolve,onReject){
}
MyPromise.prototype.catch = function(onReject){
}
function MyPromise(){
}
window.MyPromise = MyPromise
})(window)
第三步:给 MyPromise 构造函数加上 resolve 和 reject 函数方法
代码更新实现如下:
(function(window){
MyPromise.prototype.then = function(onResolve,onReject){
}
MyPromise.prototype.catch = function(onReject){
}
function MyPromise(executor){
function resolve(){
}
function reject(){
}
executor(resolve,reject) //将resolve 和 reject 作为实参传给实例对象的回调,然后调用掉
}
window.MyPromise = MyPromise
})(window)
同时,这里咱们放上一个执行器(其实也就是一个函数),将 resolve 和 reject作为实参传给它的实例对象的回调;
第四步:添加 Promise 状态
Promise 有三种状态,分别是 pending 、 fulfilled 以及 rejected 状态,
当然 fulfilled 状态我们一般更习惯叫 resolved 状态,
它们分别对应着初始 Promise 的状态的 pending 状态
resolve 方法改变 Promise 状态之后的 resolved 状态
以及 reject 方法改变 Promise状态之后的 rejected 状态
(function(window){
MyPromise.prototype.then = function(onResolve,onReject){
//把回调存放在callbacks数组里去
let obj = {};
obj.onResolve = onResolve;
ovj.onReject = onReject;
this.callbacks.push(obj)
}
MyPromise.prototype.catch = function(onReject){
}
function MyPromise(executor){
let self = this //将MyPromise里的指针赋值出来使用;
self.status = 'pending' //记录此时我们的Promise状态,初始状态为'pending';
self.data = undefined //定义一个存储变量,为将来传参的存储做准备;
self.callbacks = [] //定义一个数组,存储触发的是 resolve 回调还是 reject 回调;
function resolve(value){
if(self.status !== 'pending'){
return //如果MyPromise不是初始状态pending,那么我们直接结束,因为状态改变后就不可逆了
}
self.status = 'resolved' //变更MyPromise状态;
self.data = value //将传进来的实参储存起来
//有没有待执行的callback函数
if(self.callbacks.length>0){
setTimeout(()=>{ //放置定时器为了让里面的代码变成宏任务,防止resolve执行完之后执行 .then 发现数组是空的这种情况,故强行定时器让两者保持先后执行
self.callbacks.forEach(callbackObj => {
callbackObj.onResolve(self.data) //进行传参
})
})
}
}
function reject(){
if(self.status !== 'pending'){
return //如果MyPromise不是初始状态pending,那么我们直接结束,因为状态改变后就不可逆了
}
self.status = 'rejected' //变更MyPromise状态;
self.data = value //将传进来的实参储存起来
//有没有待执行的callback函数
if(self.callbacks.length>0){
setTimeout(()=>{ //放置定时器为了让里面的代码变成宏任务,防止reject执行完之后执行 .then 发现数组是空的这种情况,故强行定时器让两者保持先后执行
self.callbacks.forEach(callbackObj => {
callbackObj.onReject(self.data) //进行传参
})
})
}
}
executor(resolve,reject)
}
window.MyPromise = MyPromise
})(window)
第五步:处理中间报错之后reject会不会去执行
官方的 Promise 是有这样一个特点的,如果代码在 Promise 对象里还未执行到 resolve 或者 reject 函数方法就出了错误或者bug,那么它仍然会执行reject进行报错;
你是不是不相信?那么咱们一起来看一下。
执行以下代码:
let p = new Promise((resolve,reject)=>{
throw Error('err')
})
p.then(
(res) =>{
console.log('res',res);
},
(error) =>{
console.log('error',error);
}
)
p.catch(
(err) => {
console.log(err,'err')
}
)
将会出现两个错误,一个是咱们人为抛出的错误,还有一个则是reject里返回的错误,如下图:
这说明什么?说明即便是 Promise 里面的代码出了问题, reject 也仍旧会执行。
那么咱们也要更加完善,把这部分功能加上。
即将executor(resolve,reject)的调用用异常抛出包裹
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
但是写到这里还是有问题的,因为当执行到 .then 的时候,可能 MyPromise 实例出来的对象状态是'pending', 也可能是 'resolved';而且还存在异步问题,resolve方法里面的判断语句if(self.callbacks.length>0) 也不可能成立
第六步:处理执行到 .then 时,MyPromise状态问题
这里我们只需要将 MyPromise 原型上的 then 函数里的代码改成以下即可:
MyPromise.prototype.then = function(onResolve,onReject){
let self = this;
if(self.status === 'pending'){
self.callbacks.push({
onResolve,
onReject
})
}else if(self.status === 'resolved'){
setTimeout(() => {
onResolve(self.data)
})
}else{
setTimeout(() => {
onReject(self.data)
})
}
}
将以上代码补充入整份代码后,完善后完整代码如下:
(function(window){
MyPromise.prototype.then = function(onResolve,onReject){
let self = this;
if(self.status === 'pending'){
self.callbacks.push({
onResolve,
onReject
})
}else if(self.status === 'resolved'){
setTimeout(() => {
onResolve(self.data) //如果执行不到resolve里面的判断,则自己将其值传进来
})
}else{
setTimeout(() => {
onReject(self.data)
})
}
}
MyPromise.prototype.catch = function(onReject){
}
function MyPromise(executor){
let self = this
self.status = 'pending'
self.data = undefined
self.callbacks = []
function resolve(value){
if(self.status !== 'pending'){
return
}
self.status = 'resolved'
self.data = value
if(self.callbacks.length>0){
setTimeout(()=>{
self.callbacks.forEach(callbackObj => {
callbackObj.onResolve(self.data)
})
})
}
}
function reject(){
if(self.status !== 'pending'){
return
}
self.status = 'rejected'
self.data = value
if(self.callbacks.length>0){
setTimeout(()=>{
self.callbacks.forEach(callbackObj => {
callbackObj.onReject(self.data)
})
})
}
}
executor(resolve,reject)
}
window.MyPromise = MyPromise
})(window)
将这份代码拿到浏览器上去执行,效果如下, .then 里是可以打印出来我们向 resolve 传的值的:
那么第一大步我们就算是做完了,剩下的就是完成如何在 .then 后面继续接 .then ,让其可以一直接 .then
第七步:实现.then 后继续接 .then
我们知道在官方的 .then 里面其实是会隐式的返回出来一个 Promise 对象的,
所以我们这里要做的第一步就是不管三七二十一先在 .then 里面返回出来一个 MyPromise 对象。
再在里面做操作。
MyPromise.prototype.then = function(onResolve,onReject){
let self = this;
return new MyPromise((resolve,reject) =>{
if(self.status === 'pending'){
self.callbacks.push({
onResolve,
onReject
})
}else if(self.status === 'resolved'){
setTimeout(() => {
const result = onResolve(self.data)
if(result instanceof MyPromise){ //.then里的回调函数没有额外的return
result.then( //为了将result状态变更成resolved
(val) =>{resolve(val)},
(err) =>{reject(err)}
)
return result
}else{
resolve(result);
}
})
}else{
setTimeout(() => {
onReject(self.data)
})
}
}
}
写到这里,一个简单的手写 Promise 就算是实现了,如下是完整代码:
(function(window){
MyPromise.prototype.then = function(onResolve,onReject){
let self = this;
return new MyPromise((resolve,reject) =>{
if(self.status === 'pending'){
self.callbacks.push({
onResolve,
onReject
})
}else if(self.status === 'resolved'){
setTimeout(() => {
const result = onResolve(self.data)
if(result instanceof MyPromise){
result.then(
(val) =>{resolve(val)},
(err) =>{reject(err)}
)
return result
}else{
resolve(result);
}
})
}else{
setTimeout(() => {
onReject(self.data)
})
}
}
}
MyPromise.prototype.catch = function(onReject){
}
function MyPromise(executor){
let self = this
self.status = 'pending'
self.data = undefined
self.callbacks = []
function resolve(value){
if(self.status !== 'pending'){
return
}
self.status = 'resolved'
self.data = value
if(self.callbacks.length>0){
setTimeout(()=>{
self.callbacks.forEach(callbackObj => {
callbackObj.onResolve(self.data)
})
})
}
}
function reject(){
if(self.status !== 'pending'){
return
}
self.status = 'rejected'
self.data = value
if(self.callbacks.length>0){
setTimeout(()=>{
self.callbacks.forEach(callbackObj => {
callbackObj.onReject(self.data)
})
})
}
}
executor(resolve,reject)
}
window.MyPromise = MyPromise
})(window)
那么我们来看看效果吧:
可以看到已经完成一个简单的手写 Promise.then 了,这篇文章也就到此为止啦,看完不妨点个小赞吧。