1 简易版
我们先来使用下原生的Promise,然后在使用的过程中注意几个点,再根据这些点来自己实现一遍Promise。
const p0 = new Promise((resolve, reject) => {
resolve(1);
});
p0.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
上述代码很简单,p0是个新的Promise实例,Promise构造函数接受一个执行器函数,执行器函数会接受两个参数,一个是resolve函数,用来将Promise实例p0的状态由pending改成resolved。 接着,调用p0的then函数,then函数接受两个函数,第一个函数会在p0的状态变成resolved的时候触发执行,第二个函数会在p0的状态变成rejected的时候执行。 由此,我们总结一下Promise构造函数的几个点:
-
Promise构造函数接受一个执行器函数,该执行器函数会接受两个函数参数,分别用来改变Promise实例的状态。 -
同时,Promise实例的状态只能被改变一次,也就是只能从
pengding->resolved,或者从pending到rejected,不能是resolved->resolved,或者rejected->rejected,因此我们在改变状态的时候需要先判断一下。根据这以上这两点,我们可以写出如下代码:
function _Promise(executor) { this.status = 'pending'; function resolve () { if (this.status === 'pending') { this.status = 'resolved'; } } function reject () { if (this.status === 'pending') { this.status = 'rejected'; } } excutor(resolve, reject); }不知道大家发现没有,这个代码是有
this指向问题的,_Promise是个构造函数,因此最外面用this没问题,这个this会指向new _Promise(xx)生成的对象,也就是p0。但是在resolve函数和reject函数内部,其this是指向全局变量的。因为函数中this的指向,要么指向调用函数的对象,要么指向被apply之类绑定的对象,最后就是指向全局变量了,我们这里符合的是最后一种情况,这一点如果大家不理解,可以先暂停去了解一下。因此,我们需要修改下代码,在外面保存this,然后再传进resolve和reject函数内部。function _Promise(executor) { const _self = this; _self.status = 'pending'; function resolve () { if (_self.status === 'pending') { _self.status = 'resolved'; } } function reject () { if (_self.status === 'pending') { _self.status = 'rejected'; } } executor(resolve, reject); } -
resolve和reject是需要接受参数的,因此我们还需要改进下,把resolve和reject函数的的入参保存起来。另外,在执行executor的时候,有可能会报错,因此我们需要用try-catch包裹executor的执行,在报错的时候执行reject函数,毕竟报错也会导致Promise状态的改变。改进后的代码如下:function _Promise(executor) { const _self = this; _self.status = 'pending'; _self.value = undefined; // 保存resolve函数的入参 _self.reason = undefined; // 保存reject函数的入参 function resolve(res) { if (_self.status === 'pending') { _self.status = 'resolved'; _self.value = res; } } function reject(err) { if (_self.status === 'pending') { _self.status = 'rejected'; _self.reason = err; } } try { executor(resolve, reject); } catch (e) { reject(e); } } -
到这里,我们的执行器就差不多了,当然这只是简易版的,后续还会继续优化。接下来我们实现
then方法:Promise实例是有then方法的,因此,我们需要在自定义的_Promise的原型链上实现then方法,这样实例化后的p0才能调用then方法。_Promise.prototype.then = function () {} -
接着,
then方法接受两个参数,这两个参数都是函数,第一个参数会在p0的状态变成resolved的时候触发,第二个参数会在p0的状态变成rejected的时候触发。我们调用then的方式,是p0.then(xxx),因此,在then内部,是可以通过this获取到p0的。根据这一点,我们来实现下then方法:_Promise.prototype.then = function (onResolved, onRejected) { const _self = this; switch (_self.status) { case 'resolved': onResolved(_self.value); break; case 'rejected': onRejected(_self.reason); break; default: break; } };上述代码中,我们同样在第二行用
_self获取了p0对象,然后判断当前p0对象的状态,如果是resolved的话,就执行then函数的第一个函数参数onResolved,并传入_self.value,如果是rejected的话,就执行第二个函数参数onRejected,并传入_self.reason。
以上就是简易版Promise的实现了,我们实现了构造器函数,实现了原型链上的then函数,是不是非常简单?现在让我们测试一下:
const p0 = new _Promise((resolve, reject) => {
resolve(1);
});
p0.then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
这个测试代码跟我们最开始使用原生Promise只有一点不同,那就是构造函数换成了我们自定义的_Promise,而最后的执行结果,跟使用原生Promise的一模一样!!
这就是简易版的Promise,之所以说是简易版,是因为它是有缺陷的,它不支持异步调用,也就是说,如果在构造函数_Promise中异步执行resolve(),那它是不能正常工作的,测试代码如下:
const p0 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
});
p0.then((res) => {
console.log(res);
});
// 没有输出1
它还有其他问题,比如说还不支持链式调用,不过我们一步一步来,目前先解决下这个异步调用的先。
2 修复构造函数中异步处理
我们先分析下为什么异步调用不能如期工作:
- 首先,我们在
new _Promise(executor)的时候,执行器executor是会被同步执行的,只是resolve(1)由于被setTimeout包裹,因此变成了异步。 - 其次,同步执行完
executor之后,会马上同步执行then方法,而我们在then方法里面,会根据当前实例的状态,如果是resolved,就执行then的第一个参数,也就是onResolved方法,如果是rejected,就执行then的第二个参数,也就是onRejected方法。 - 但是现在,由于
resolve(1)被异步了,因此在同步执行then方法的时候,resolve(1)还没被执行,此时当前实例的状态还是pending,而我们在switch case中没有判断pending状态该怎么处理,因此就会在pending状态这里结束了,也就没有输出1了。
明白了原因,那我们就来修复一下then,在switch case的时候补充一个pending状态就好了。
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
switch (_self.status) {
case 'resolved':
onResolved(_self.value);
break;
case 'rejected':
onRejected(_self.reason);
case 'pending':
_self.onResolvedCallbacks.push(() => onResolved(_self.value));
_self.onRejectedCallbacks.push(() => onRejected(_self.reason));
break;
default:
break;
}
};
在pending状态的情况下,我们用箭头函数封装一下onResolved(_self.value),然后push进onResolvedCallbacks,对于onRejected也做同样的处理。 这里我们引入了新的变量onResolvedCallbacks用来存放pending状态下onResolved函数,这个变量是数组,为什么是数组呢?因为p0是可以多次调用then方法的,比如:
const p0 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
});
p0.then((res) => {
console.log(res);
});
p0.then((res) => {
console.log(res);
});
在使用原生Promise的时候,这种情况下会输出两次1。因此,我们的存放pending状态下onResolved函数的变量onResolvedCallbacks也要是一个数组,这样才能满足这种异步resolve下多次调用then的情况,否则,如果是常规的变量,多次调用then时,后者就会覆盖前者。
case 'pending':
_self.onResolvedCallbacks = () => onResolved(_self.value);
_self.onRejectedCallbacks = () => onRejected(_self.reason);
break;
这种情况下,要是异步resolve下多次调用then,由于此时都是pengding状态,那么后者onResolvedCallbacks就会覆盖前者,所以要用数组。
既然使用了新的变量,那么我们就要改一下构造函数_Promise了。
function _Promise(executor) {
const _self = this;
_self.status = 'pending';
_self.value = undefined; // 保存resolve函数的入参
_self.reason = undefined; // 保存reject函数的入参
_self.onResolvedCallbacks = []; // 保存pending状态下的onResolved函数
_self.onRejectedCallbacks = []; // 保存pending状态下的onRejected函数
function resolve(res) {
if (_self.status === 'pending') {
_self.status = 'resolved';
_self.value = res;
// 一旦 resolve 执行,遍历执行已收集到的 onResolved 回调函数,并重置 onResolvedCallbacks
_self.onResolvedCallbacks.forEach((fn) => fn());
_self.onResolvedCallbacks = [];
}
}
function reject(err) {
if (_self.status === 'pending') {
_self.status = 'rejected';
_self.reason = err;
// 一旦 reject 执行,遍历执行已收集到的 onRejectedCallbacks 回调函数,并重置 onRejectedCallbacks
_self.onRejectedCallbacks.forEach((fn) => fn());
_self.onRejectedCallbacks = [];
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
我们在构造函数里加了两个变量:用来保存pending状态下的onResolved函数的onResolvedCallbacks变量,以及保存pending状态下的onRejected函数的onRejectedCallbacks变量。 接着,在resolve或者reject的时候,遍历onResolvedCallbacks或者onRejectedCallbacks变量,执行里面保存的函数。 这样,我们就能实现异步resolve()时也能正常调用then了!
完整的代码如下:
function _Promise(executor) {
const _self = this;
_self.status = 'pending';
_self.value = undefined; // 保存resolve函数的入参
_self.reason = undefined; // 保存reject函数的入参
_self.onResolvedCallbacks = []; // 保存pending状态下的onResolved函数
_self.onRejectedCallbacks = []; // 保存pending状态下的onRejected函数
function resolve(res) {
if (_self.status === 'pending') {
_self.status = 'resolved';
_self.value = res;
// 一旦 resolve 执行,遍历执行已收集到的 onResolved 回调函数,并重置 onResolvedCallbacks
_self.onResolvedCallbacks.forEach((fn) => fn());
_self.onResolvedCallbacks = [];
}
}
function reject(err) {
if (_self.status === 'pending') {
_self.status = 'rejected';
_self.reason = err;
// 一旦 reject 执行,遍历执行已收集到的 onRejectedCallbacks 回调函数,并重置 onRejectedCallbacks
_self.onRejectedCallbacks.forEach((fn) => fn());
_self.onRejectedCallbacks = [];
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
switch (_self.status) {
case 'resolved':
onResolved(_self.value);
break;
case 'rejected':
onRejected(_self.reason);
case 'pending':
_self.onResolvedCallbacks.push(() => onResolved(_self.value));
_self.onRejectedCallbacks.push(() => onRejected(_self.reason));
break;
default:
break;
}
};
const p0 = new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
});
p0.then((res) => {
console.log(res);
});
p0.then((res) => {
console.log(res);
});
执行后,我们的终端也能正常输出两次1啦!!恭喜你,我们实现了异步的Promise:核心就是pending状态时收集then的回调函数onResolved和onRejected,然后在resolve或者reject的时候遍历执行收集到的onResolved或者onRejected。
不过到这里,还没结束,我们的_Promise.then不支持链式调用,要支持链式调用的话,需要在then里返回一个新的_Promise对象。那就,返回呗!
3 修复链式调用
刚才我们分析了下,若要链式调用then方法的话,需要then返回一个_Promise对象,而我们前面这版并没有做到这点,因此无法链式调用then方法。
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
switch (_self.status) {
case 'resolved':
onResolved(_self.value);
break;
case 'rejected':
onRejected(_self.reason);
case 'pending':
_self.onResolvedCallbacks.push(() => onResolved(_self.value));
_self.onRejectedCallbacks.push(() => onRejected(_self.reason));
break;
default:
break;
}
return new _Promise((resolve, reject) => {});
};
但是,好家伙,then方法现在已经有部分逻辑了,要返回新的_Promise实例的话,应该在哪里插入呢?像上面一样,在最后一行返回吗?肯定不是对吧。我们先用原生的Promise,链式调用下then,感受下then的逻辑:
const p0 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
});
p0.then((res) => {
console.log('监听p0 resolved状态的返回值', res);
}).then((res) => {
console.log('监听p1 resolved状态的返回值', res);
});
上述代码输出结果如下:
监听p0 resolved状态的返回值 1
监听p1 resolved状态的返回值 undefined
为了方便描述,我们将第一个then里的返回的Promise称为P1,将第二个then里返回的Promise称为P2,最开始new出来的是p0,也就是下面这样:
从这里我们可以看出,then里的swich case的逻辑,要放在new _Promise里的:
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
switch (_self.status) {
case 'resolved':
onResolved(_self.value);
// 调用完onResolved之后,根据结果决定是触发 resolve 或者 reject
break;
case 'rejected':
onRejected(_self.reason);
case 'pending':
_self.onResolvedCallbacks.push(() => onResolved(_self.value));
_self.onRejectedCallbacks.push(() => onRejected(_self.reason));
break;
default:
break;
}
});
};
只有这样,我们才能在调用then的第一个参数onResolved或者第二个参数onRejected的时候,触发new _Promise的resolve或者reject,改变返回的_Promise的实例的状态。 对于P1来说,它触发的是case ‘resolved’这个情况,因此会执行onResolved(_self.value), 此时的_self是p0,所以_self.value就是1。
接下来,我们根据onResolved(_self.value)的返回结果,决定是触发new _Promise的resolve还是reject 。如果结果没报错,那么就是触发resolve,否则就是触发reject。
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
const callback = (result) => {
try {
resolve(result);
} catch (e) {
reject(e);
}
};
switch (_self.status) {
case 'resolved':
callback(onResolved(_self.value));
break;
case 'rejected':
onRejected(_self.reason);
case 'pending':
_self.onResolvedCallbacks.push(() => onResolved(_self.value));
_self.onRejectedCallbacks.push(() => onRejected(_self.reason));
break;
default:
break;
}
});
};
我们在case 'resolved'的时候,将onResolved(_self.value)的执行结果传到callback函数里,然后在callback里对该结果进行判断,没报错就resolve,报错就reject。 但是,这样是有问题的,我们是把onResolved(_self.value)的结果传到callback里了,而不是整个onResolved的执行过程,如果在执行onResolved的过程中报错了,那并不会进入到callback的逻辑,报错也就不会被callbakc捕捉了,因此,我们需要修改下,将整个onResolved传入到callback中,然后在callback中执行onResolved,这样执行onResolved的过程中报错了才能被try-catch捕捉到。
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
const callback = (fn) => {
try {
const result = fn(_self.value); // 执行传入的 onResolved
resolve(result);
} catch (e) {
reject(e);
}
};
switch (_self.status) {
case 'resolved':
callback(onResolved); // 将 onResolved 传入 callback 中
break;
case 'rejected':
onRejected(_self.reason);
case 'pending':
_self.onResolvedCallbacks.push(() => callback(onResolved)); // 同样,这里也要将 onResolved 传入 callback 中
_self.onRejectedCallbacks.push(() => onRejected(_self.reason));
break;
default:
break;
}
});
};
在上述代码中,我们在命中case 'resolved'时将onResolved传入callback,同样,在pending收集onResolvedCallbacks时,也是将onResolved传入到callback中,这样一来,所有onResolved的情况都能被try-catch捕捉了。 同样,对于case 'rejected'的处理也是跟onResolved的一样,也要将onRejected传入callback进行处理。
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
const callback = (fn) => {
try {
const result = fn(_self.value || _self.reason);
resolve(result);
} catch (e) {
reject(e);
}
};
switch (_self.status) {
case 'resolved':
callback(onResolved);
break;
case 'rejected':
callback(onRejected);
case 'pending':
_self.onResolvedCallbacks.push(() => callback(onResolved));
_self.onRejectedCallbacks.push(() => callback(onRejected));
break;
default:
break;
}
});
};
这里我们稍微做了下优化,在第6行代码中,将_self.value和_self.reason通过或逻辑一起传递给fn了,之所以能这样做,是因为如果传入的是onRejected,执行的就是_Promise()构造函数里的resject方法:
function _Promise(executor) {
const _self = this;
_self.status = 'pending';
_self.value = undefined; // 保存resolve函数的入参
_self.reason = undefined; // 保存reject函数的入参
// 其他代码
function reject(err) {
_self.status = 'rejected';
_self.reason = err;
// 其他代码
}
// 其他代码
}
此时_self.value肯定是undefined。 同样,反过来如果传入的是onResolved,那么_self.value肯定有值。
4. 修复then状态与onResolved同步的问题
到这一步,我们的then方法的实现就完成90%了,还差10%是没考虑这种情况:如果then返回的Promise里的callback,也就是下面的代码,
const result = fn(_self.value || _self.reason)
fn的执行结果result如果是一个_Promise对象怎么处理呢? 也就是下面这种情况,我们用原生的Promise看一下输出:
const p0 = new Promise((resolve, reject) => resolve());
p0.then((res) => {
return new Promise((resolve, reject) => reject('p1 的 onResolved 里返回了新的 Promise,其状态是 rejected'));
}).then(
(res) => {
console.log('监听p1 resolved 状态的返回值');
console.log(e);
},
(e) => {
console.log('监听p1 rejected 状态的返回值');
console.log(e);
}
);
上述代码输出如下:
监听p1 rejected 状态的返回值
p1 的 onResolved 里返回了新的 Promise,其状态是 rejected
根据上述输出,我们可以得知:如果then的第一个参数函数onResolved里返回了新的Promise(称为p1-1),那么then返回的Promise的状态,也就是这里的p1,p1的状态是跟p1-1的状态保持一致的。 注意这里有两个返回的Promise,一个是then本身执行结果返回的Promise,一个是then的第一个参数函数onResolved返回的Promise,前者的状态会跟后者保持一致。
然后,我们再换成我们刚实现的_Promise:
const p0 = new _Promise((resolve, reject) => resolve());
p0.then((res) => {
return new _Promise((resolve, reject) => reject('p1 的 onResolved 里返回了新的 Promise,其状态是 rejected'));
}).then(
(res) => {
console.log('监听p1 resolved 状态的返回值');
console.log(res);
},
(e) => {
console.log('监听p1 rejected 状态的返回值');
console.log(e);
}
);
这里有两个new Promise要改成new _Promise:其输出如下:
监听p1 resolved 状态的返回值
_Promise {
status: 'rejected',
value: undefined,
reason: 'p1 的 onResolved 里返回了新的 Promise,其状态是 rejected',
onResolvedCallbacks: [],
onRejectedCallbacks: []
}
根据输出结果,可以看到它走的是p2的onResolved的逻辑,因此p1的状态是resolved,但是p1的onResolved里返回的Promise(也就是p1-1)的状态是rejected,也就是说,p1的状态跟p1-1不一致了,then返回的Promise,跟then的入参函数onResolved返回的Promise的状态不一致!! 这是什么原因呢?!我们来看下我们实现的_Promise.prototype.then,重点看callback:
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
const callback = (fn) => {
try {
const result = fn(_self.value || _self.reason);
resolve(result);
} catch (e) {
reject(e);
}
};
// 其他代码
};
在callback里,我们只是根据fn的执行结果进行resolved了,而没有判断fn返回新的_Promise实例的情况,这就导致我们的then返回的Promise的状态跟then的第一个参数onResolved返回的Promise的状态不一致的原因。 知道了原因,修复起来也就容易了,我们只需要增加一个判断,判断fn的执行结果result是不是_Promise实例,如果是的话,就让then返回的Promise的状态跟该实例的状态保持一致即可。
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
const callback = (fn) => {
try {
const result = fn(_self.value || _self.reason);
if (result instanceof _Promise) {
result.then((res) => resolve(res), (err) => reject(err));
return;
}
resolve(result);
} catch (e) {
reject(e);
}
};
// 省略 switch case 逻辑
};
这里我们判断如果fn的返回结果result是_Promise实例的话,就在result.then里同步_Promise.prototype.then返回的Promise的状态,注意这里在第9行还加了个return,从而结束函数。 就这一个改动,是不是很简单? 然后,我们再执行以下代码测试一下修改后的then:
const p0 = new _Promise((resolve, reject) => resolve());
p0.then((res) => {
return new _Promise((resolve, reject) => reject('p1 的 onResolved 里返回了新的 Promise,其状态是 rejected'));
}).then(
(res) => {
console.log('监听p1 resolved 状态的返回值');
console.log(res);
},
(e) => {
console.log('监听p1 rejected 状态的返回值');
console.log(e);
}
);
上述代码执行结果如下:
监听p1 rejected 状态的返回值
p1 的 onResolved 里返回了新的 Promise,其状态是 rejected
奈斯!!!非常好!p1 的 onResolved 里返回了个 rejected 状态的 Promise,p1的状态也变成了rejected,两者同步了! 完整代码放在下面给大家了:
function _Promise(executor) {
const _self = this;
_self.status = 'pending';
_self.value = undefined; // 保存resolve函数的入参
_self.reason = undefined; // 保存reject函数的入参
_self.onResolvedCallbacks = []; // 保存pending状态下的onResolved函数
_self.onRejectedCallbacks = []; // 保存pending状态下的onRejected函数
function resolve(res) {
if (_self.status === 'pending') {
_self.status = 'resolved';
_self.value = res;
// 一旦 resolve 执行,遍历执行已收集到的 onResolved 回调函数,并重置 onResolvedCallbacks
_self.onResolvedCallbacks.forEach((fn) => fn());
_self.onResolvedCallbacks = [];
}
}
function reject(err) {
if (_self.status === 'pending') {
_self.status = 'rejected';
_self.reason = err;
// 一旦 reject 执行,遍历执行已收集到的 onRejectedCallbacks 回调函数,并重置 onRejectedCallbacks
_self.onRejectedCallbacks.forEach((fn) => fn());
_self.onRejectedCallbacks = [];
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
_Promise.prototype.then = function (onResolved, onRejected) {
const _self = this;
return new _Promise((resolve, reject) => {
const callback = (fn) => {
try {
const result = fn(_self.value || _self.reason);
if (result instanceof _Promise) {
// prettier-ignore
result.then((res) => resolve(res), (err) => reject(err));
return;
}
resolve(result);
} catch (e) {
reject(e);
}
};
switch (_self.status) {
case 'resolved':
callback(onResolved);
break;
case 'rejected':
callback(onRejected);
case 'pending':
_self.onResolvedCallbacks.push(() => callback(onResolved));
_self.onRejectedCallbacks.push(() => callback(onRejected));
break;
default:
break;
}
});
};
const p0 = new _Promise((resolve, reject) => resolve());
p0.then((res) => {
return new _Promise((resolve, reject) => reject('p1 的 onResolved 里返回了新的 Promise,其状态是 rejected'));
}).then(
(res) => {
console.log('监听p1 resolved 状态的返回值');
console.log(res);
},
(e) => {
console.log('监听p1 rejected 状态的返回值');
console.log(e);
}
);
以上就是我们Promise以及Promise.prototype.then的实现逻辑,我们实现了Promise的状态的改变、异步处理、then的链式调用以及then的状态与onResolved同步问题。 整个文章逻辑应该是比较清晰的,如果有讲的不清楚或者讲错的地方,欢迎你评论区留言~我看到的话会及时回复的。
以上就是这篇文章的全部内容了,如果对你有帮助的话,还请点个赞或者收藏支持一下吧~
彩蛋
其实我们实现的then还有个小问题,then里的回调函数,是会异步执行的,而我们的then,目前还是同步执行。我们来测试一下:
const p0 = new Promise((resolve) => {
console.log(1);
resolve(3);
});
p0.then((val) => {
console.log(val);
});
console.log(2);
上述代码正常输出是:1 2 3
然后,我们再换成_Promise试试:
const p0 = new _Promise((resolve) => {
console.log(1);
resolve(3);
});
p0.then((val) => {
console.log(val);
});
console.log(2);
其输出如下:诶?!变成 1 3 2 了?
这可咋搞?!
如果你有解决方案的话,也欢迎评论区留言哦,我们会在下篇文章解决这个问题,并继续实现Promise的catch、race、all等方法。