手撕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