写在前面
Promise
是ES6
中核心的语法,也是面试最常问的一部分,所以搞懂它的执行规则是必须,学会手写会让你在面试中脱颖而出,迈出拿到offer
坚实的一步~
在之前的文章中,我翻译了Promise A+
规范,在阅读本文之前,建议先阅读一下:【Promise】Promises/A+中文翻译
手写Promise
Promise概览
Promise
是一个管理异步编程的方案,它是一个构造函数,每次使用可用new
创建实例;它有三种状态:pending
、fulfilled
和rejected
,这三种状态不会受外界影响,状态只能由pending
变为fullfilled
(成功),pending
变为rejected
(失败),且一旦改变就不会再改变,在状态改变后,它会返回成功的结果或者失败的原因,它对外抛出了resolve
、reject
、catch
、finally
、then
、all
、race
、done
,在最新的提案中,添加了allSettled
方法,它不管成功、失败都会返回,接下来,我们自己实现整个Promise
executor函数
我们知道,在创建一个Promise实例时,都会立即执行executor
函数,executor
函数传递两个参数,resolve
和reject
,如果executor
函数执行错误,Promise
实例状态会变为rejected
class MyPromise{
constructor(executor) {
this.status = "pending"; // 初始化状态为pending
this.value = undefined; // 初始化返回的成功的结果或者失败的原因
// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
let resolve = result => {
if(this.status !== "pending") return; // 状态一旦改变,就不会再变
this.status = "resolved";
this.value = result;
}
// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
let reject = reason => {
if(this.status !== "pending") return;
this.status = "rejected";
this.value = reason;
}
// try、catch捕获异常,如果错误,执行reject方法
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
}
我们来验证一下,现在的Promise
是什么样的
let p1 = new MyPromise((resolve, reject) => {
resolve(1);
})
let p2 = new MyPromise((resolve, reject) => {
reject(2);
})
console.log(p1);
console.log(p2);
可以看到,状态已经改变了,里面的值也是成功的结果和失败的原因。then
方法有两个参数,第一个参数是成功时执行的,第二个参数为失败后执行的,then
的链式调用和数组等是一样的,每次执行后会返回一个Promise
实例。如果成功后,第一个then
中成功的函数为null
,它会继续向下查找,直至不为null
的函数执行,上一个then
中返回的结果会直接影响下一个then
中执行成功或者失败的哪个函数,了解了这些之后,我们尝试实现一下~
then方法
then(resolveFn, rejectFn) {
// 如果传入的两个参数不是函数,则直接执行返回结果
let resolveArr = [];
let rejectArr = [];
if(typeof resolveFn !== "function") {
resolveFn = result => {
return result;
}
}
if(typeof rejectFn !== "function") {
rejectFn = reason => {
return MyPromise.reject(reason);
}
}
return new Mypromise((resolve, reject) => {
resolveArr.push(result => {
try {
let x = resolveFn(result);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
rejectArr.push(reason => {
try {
let x = rejectFn(reason);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
})
}
我们来整理一下上面的代码
class MyPromise{
constructor(executor) {
this.status = "pending"; // 初始化状态为pending
this.value = undefined; // 初始化返回的成功的结果或者失败的原因
this.resolveArr = []; // 初始化then中成功的方法
this.rejectArr = []; // 初始化then中失败的方法
// 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多🤔
let change = (status, value) => {
if(this.status !== "pending") return; // 状态一旦改变,就不会再变
this.status = status;
this.value = value;
// 根据状态判断要执行成功的方法或失败的方法
let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr;
// fnArr中的方法依次执行
fnArr.forEach(item => {
if(typeof item !== "function") return;
item(this. value);
})
}
// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
let resolve = result => {
change("resolved", result)
}
// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
let reject = reason => {
change("rejected", reason);
}
// try、catch捕获异常,如果错误,执行reject方法
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
then(resolveFn, rejectFn) {
// 如果传入的两个参数不是函数,则直接执行返回结果
if(typeof resolveFn !== "function") {
resolveFn = result => {
return result;
}
}
if(typeof rejectFn !== "function") {
rejectFn = reason => {
return MyPromise.reject(reason);
}
}
return new MyPromise((resolve, reject) => {
this.resolveArr.push(result => {
try {
let x = resolveFn(result); // 获取执行成功方法返回的结果
// 如果x是一个promise实例,则继续调用then方法 ==> then链的实现
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
// 不是promise实例,直接执行成功的方法
resolve(x);
} catch(err) {
reject(err)
}
})
this.rejectArr.push(reason => {
try {
let x = rejectFn(reason);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
})
}
}
我们来看一下效果
new MyPromise((resolve, reject) => {
resolve(1);
}).then(res => {
console.log(res, 'success');
}, err => {
console.log(err, 'error');
})
这时候,问题出现了,我们发现好像什么也没有输出,如果我们对上面的测试例子做一下小小的改动呢?
new MyPromise((resolve, reject) => {
setTimeout(_ => {
resolve(1);
}, 0)
}).then(res => {
console.log(res, 'success'); // 1 "success"
}, err => {
console.log(err, 'error');
})
这是因为创建了Promise
实例就立即执行了executor
函数,还没有执行then
方法,那么不管成功还是失败的数组中,都是空的。那可能小伙伴们又有疑问了,为什么加了setTimeout
就好使了呢?这是因为在事件队列机制中,setTimeout
会放入事件队列中,等主线程执行完成后再执行,此时then
方法会存储成功或者失败的函数,所以不管是成功的数组还是失败的数组中都已经有值了,这个时候再去执行就完全👌了~
但是我们不能在使用的时候写setTimeout
当做解决方案呀,既然我们在封装,就要在封装的函数内解决问题,按照这样的思路,我们也同样可以在resolve
和reject
方法执行的时候,判断数组中是否有值,如果没有,我们可以利用setTimeout
让它延后执行,代码如下~
// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
let resolve = result => {
// 如果数组中有值,则立即改变状态
if(this.resolveArr.length > 0) {
change("resolved", result)
}
// 如果没值,则延后改变状态
let timer = setTimeout(_ => {
change("resolved", result)
clearTimeout(timer);
}, 0)
}
// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
let reject = reason => {
// 如果数组中有值,则立即改变状态
if(this.rejectArr.length > 0) {
change("rejected", reason);
}
// 如果没值,则延后改变状态
let timer = setTimeout(_ => {
change("rejected", reason);
clearTimeout(timer);
}, 0)
}
现在我们再试一下
// 1、已经成功了
new MyPromise((resolve, reject) => {
resolve('我成功啦,吼吼吼~~~~');
reject('我都已经成功了,你别想让我失败,哼~~');
}).then(res => {
console.log(res, 'success'); // 我成功啦,吼吼吼~~~~ success
}, err => {
console.log(err, 'error');
})
// 2、先失败了
new MyPromise((resolve, reject) => {
reject('失败了,我好委屈,呜呜呜~~');
resolve('已经失败了~~~');
}).then(res => {
console.log(res, 'success');
}, err => {
console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error
})
// 3、链式调用
new MyPromise((resolve, reject) => {
reject('失败了,我好委屈,呜呜呜~~');
resolve('已经失败了~~~');
}).then(res => {
console.log(res);
}, err => {
console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error
return '我要发奋图强,不会被困难所击倒,我要成功!!!'
}).then(res1 => {
console.log(res1, '经过不懈努力,我终于在第二次成功了~'); // 我要发奋图强,不会被困难所击倒,我要成功!!! 经过不懈努力,我终于在第二次成功了~
}, err1 => {
console.log(err1, '第二次失败');
})
这就完美解决了第一次调用,不会执行then
方法的问题。同时,实现了链式的调用。对于链式的调用,我多啰嗦两句,其实不管是数组的链式调用,都是因为上一次返回的还是此实例。
catch方法
catch
方法是捕获异常,它和then
方法的第二个回调函数是一样的
catch(rejectFn) {
return this.then(null, rejectFn)
}
resolve方法
我们知道,Promsie
也可以这样用
let p1 = MyPromise.resolve(1);
console.log(p1);
我们期望有这样一种写法,但是现在肯定会抛出错误:MyPromise.resolve
不是一个方法
现在需要我们封装一下resolve
方法,我们需要明确的是,resolve
之后,Promise
是支持再继续链式调用then
的,所以,我们需要执行resolve
方法,返回一个Promise
实例
static resolve(result) {
// 返回新的promise实例,执行promise实例中resolve方法
return new MyPromise(resolve => {
resolve(result)
})
}
reject方法
像resolve
方法一样,只不过它接收的是失败的函数
static reject(reason) {
// 返回新的promise实例,执行promise实例中reject方法
return new MyPromise((_, reject) => {
reject(reason);
})
}
done方法
ES6
标准入门一书中,对done
方法的解释是这样的:无论Promise对象的回调链以then方法还是catch方法结尾,只要最后一个方法抛出错误,都有可能无法捕获到。为此,Promise提供了一个done方法,它总是处于回掉链的尾端,保证抛出任何可能出现的错误。好了,我们知道了这个方法是干啥的,现在就开始写吧~
done(resolveFn, rejectFn) {
this.then(resolveFn, rejectFn)
.catch(reason => {
setTimeout(() => {
throw reason;
}, 0)
})
}
它可以接收fulfilled
、rejected
状态的回调函数,也可以不提供任何参数。但是无论怎样,done
方法都会捕捉到任何可能出现的错误,并向全局抛出
finally方法
finally
方法是无论成功还是失败都会执行的方法,像这样的方法还有小程序中的complete
方法等等,我们来尝试实现一下~
finally(finallyFn) {
let P = this.constructor;
return this.then(
value => P.resolve(finallyFn()).then(() => value),
reason => P.reject(finallyFn()).then(() => reason)
)
}
我们来验证一下
new MyPromise((resolve, reject) => {
reject('失败了,我好委屈,呜呜呜~~');
resolve('已经失败了~~~');
}).then(res => {
console.log(res);
}, err => {
console.log(err, 'error'); // 失败了,我好委屈,呜呜呜~~ error
return '我要发奋图强,不会被困难所击倒,我要成功!!!'
}).finally(() => {
console.log('执行了吗'); // 这里会输出"执行了吗"
})
all方法
all
方法接收一个数组,当数组中每个实例都成功时才会返回,返回的也是一个数组,每个参数为对应的promise
返回的结果,如果有一项失败了,all
方法都会返回失败
// 接收数组参数
static all(promiseList) {
// 返回新实例,调用后还可使用then、catch等方法
return new MyPromise((resolve, reject) => {
let index = 0, // 成功次数计数
results = []; // 返回的结果
for(let i = 0; i < promiseList.length; i++) {
let item = promiseList[i];
// 如果item不是promise实例
if(!(item instanceof MyPromise)) return;
item.then(result => {
index++;
results[i] = result;
if(index === promiseList.length) {
resolve(results);
}
}).catch(reason => {
reject(reason);
})
}
})
}
来验证一下
// 1.有失败的情况
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
.then(res => {
console.log(res);
}).catch(err => {
console.log(err, 'err'); // 2 "err"
})
// 2.无失败的情况
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
.then(res => {
console.log(res, 'success'); // [1, 2, 3] "success"
}).catch(err => {
console.log(err, 'err');
})
race方法
race
方法同样接收一个数组参数,里面每一项是Promise
实例,它返回最快改变状态的Promise
实例方法的结果
static race(promiseList) {
return new MyPromise((resolve, reject) => {
promiseList.forEach(item => {
if(!(item instanceof MyPromise)) return;
item.then(result => {
resolve(result);
}).catch(err => {
reject(err)
})
})
})
}
验证
// 1.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res); // 1 'success'
}).catch(err => {
console.log(err, 'err');
})
// 2.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success');
}).catch(err => {
console.log(err, 'err'); // 1 'err'
})
// 3.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success');
}).catch(err => {
console.log(err, 'err'); // 1 'err'
})
尝试实现allSettled方法
allSettled
方法也是接收数组参数,但是它无论成功或者失败,都会返回
static allSettled(promiseList) {
return new MyPromise((resolve, reject) => {
let results = [];
for(let i = 0; i < promiseList.length; i++) {
let item = promiseList[i];
if(!(item instanceof MyPromise)) return;
item.then(result => {
results[i] = result;
}, reason => {
results[i] = reason;
})
resolve(results);
}
})
}
验证
// 1.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res); // [1, 2, 3] 'success'
}).catch(err => {
console.log(err, 'err');
})
// 2.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success'); // [1, 2, 3] 'success'
}).catch(err => {
console.log(err, 'err');
})
// 3.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success'); // [1, 2, 3] 'success'
}).catch(err => {
console.log(err, 'err');
})
最后
本文实现了自己的一个Promise
,源码已上传至github
:MyPromise源码地址。有需要的小伙伴自行领取~
大家也可关注我的公众号「web前端日记」,更及时的接收到推送消息~