前言
面试官:了解Promise吗?
我:blabla...
面试官:
Promise.all的执行顺序是什么?我:懵...它不是哪个
resolved快,然后存到结果数组嘛???...
面试官:
Promise.all实际上没有执行顺序,它是并发执行的。我:✨🎉🎊🎃🎫🎨🎀🎆🎇 (原来是这样,尝试表达我的无知...)
所以,打算开始看一下Promise的面试题,开始总结...
Promise
问:对Promise的理解,为什么要有Promise?
答:Promise译作“承诺”,是ES6中异步编程的解决方案之一,它的提出是为了解决传统
ajax请求存在的回调地狱问题,Promise的链式调用降低了代码的复杂度、提高代码的可读性。
Promise有三种状态:
pending:进行中fulfilled:已成功rejected:已失败
特点:
- 对象状态不受外界的影响;
- 一旦状态改变(
pending变成fulfilled和pending变成rejected),就不会发生改变。
简单实现一个Promise
const PENDING = "pending";
const REJECTED = "rejected";
const FULLFILLED ="fullfilled";
class MyPromise{// https://zhuanlan.zhihu.com/p/183801144
constructor(executor){// new Promise((resole,reject)=>{})
this.status = PENDING;
this.value = null;
this.reason = null;
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
//传入的参数resolve被再次封装
let resolve = (value)=>{
if(this.status === PENDING){
this.status = FULLFILLED;
this.value = value;
this.resolvedCallbacks.forEach(fn=>fn())
}
}
//传入的参数reject被再次封装
let reject = (reason)=>{
if(this.status == PENDING){
this.status = REJECTED;
this.reason = reason;
this.rejectedCallbacks.forEach(fn=>fn())
}
}
}
try{
executor(resolve,reject)// 封装执行器
}catch(err){
reject(err)
}
}
/*
onFullfilled, onRejected都是外部传进来的
*/
then(onFullfilled,onRejected){
// 不是函数封装成函数
onFullfilled = typeof onFullfilled === "function"?onFullfilled : (value)=>value;
onRejected = typeof onRejected === "function"?onRejected : (error)=>error;
// 异步执行的关键,先进行状态判断
if(this.status === PENDING){
this.resolvedCallbacks.push(onFullfilled)
this.rejectedCallbacks.push(onRejected);
}
if(this.status === FULLFILLED ){
onFullfilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.reason);
}
}
catch(cb) {
this.then(null, cb);
}
finally(cb) {
return this.then(
(data) => {
return Promise.resolve(cb()).then(() => data);
},
(err) => {
return Promise.resolve(cb()).then(() => {
throw err;
});
}
);
}
}
MyPromise.resolve = function(value){
if(value instanceOf MyPromise){
return value;
}
return new MyPromise((resolve,_)=>resolve)
}
MyPromise.reject = function(value){
return new MyPromise((_,reject)=>reject)
}
let p = new MyPromise((resolve,reject)=>{//未实现链式调用值穿透
resolve(2)//reject(3)
}).then(res=>{
console.log(res)
},err=>{
console.log(err)
})
Promise实现的关键点就是链式调用和异步执行
/*
* onFullfilled, onRejected都是外部传进来的
*/
then(onFullfilled, onRejected) {
//不是函数封装成函数
onFullfilled = typeof onFullfilled === "function" ? onFullfilled : (value) => value;
onRejected = typeof onRejected === "function" ? onRejected : (error) => error;
// 实现then的链式调用
return new MyPromise((resolve, reject) => {
let fulfilled = () => {
try {
// 执行then中的fulfilled方法
const result = onFullfilled(this.value)
return result instanceof MyPromise ? result.then(resolve, reject) : resolve(result);
} catch (e) {
reject(e)
}
}
let rejected = () => {
try {
const result = onRejected(this.reason)
return result instanceof MyPromise ? result.then(resolve, reject) : reject(result)
} catch (e) {
reject(e)
}
}
// 实现异步的关键
if (this.status === PENDING) {
this.resolvedCallbacks.push(fulfilled)
this.rejectedCallbacks.push(rejected);
}
if (this.status === FULLFILLED) {
fulfilled()
}
if (this.status === REJECTED) {
rejected()
}
})
}
Promise.all和Promise.race
Promise.all():传入一个promise的iterable的对象,比如set、map、Array;返回一个resolve回调的结果数组。只要任何一个输入的promise有reject回调执行,就reject第一个抛出的错误信息。传入的iterable对象为空(Promise.all([]))是同步的,非空为异步。
问:Promise.all中任务的执行顺序?
答:Promise.all是并发的,等最慢的执行完后resolve结果数组。
区别串行和并发执行
function p1(){
return new Promise(resolve=>{
console.log('p1 start');
setTimeout(()=>{
console.log('p1 end');
},500)
})
}
function p2(){
return new Promise(resolve=>{
console.log('p2 start');
setTimeout(()=>{
console.log('p2 end');
},1000)
})
}
Promise.all([p1,p2])
如果是串行,执行应该为:
p1 start
p1 end
p2 start
p2 end
但实际的执行结果为:
p1 start
p2 start
p1 end
p2 end
说明它是并发执行的
简单实现一个Promise.all
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let count = 0;
let len = promises.length;
let result = [];
if(len === 0) resolve([])
promises.forEach((p,i)=>{
Promise.resolve(p).then(res=>{
count +=1;
result[i] = res;
if(count === len) resolve(result)
}).catch(err=>{
reject(err)
})
})
})
}
Promise.race(): 传入一个iterable对象,返回一个fulfilled/rejected的promise对象,返回的是第一个最快执行完成的promise。如果传递进去的iterable对象是空,则返回的promise将永远等待<pending>。
问:
Promise.race()返回一个promise之后,其他任务还会不会继续执行?答:会
function p1(){
return new Promise((resolve,reject)=>{
console.log('p1 start');
setTimeout(()=>{
console.log('p1 end');
reject();
},500)
})
}
function p2(){
return new Promise(resolve=>{
console.log('p2 start');
setTimeout(()=>{
console.log('p2 end');
resolve();
},1000)
})
}
Promise.race([p1(),p2()])
执行结果:
p1 start
p1 end
Uncaught (in promise) undefined
p2 start
p2 end
p1在抛出错误reject后,依旧会再执行p2。
简单实现一个Promise.race
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
promises.forEach(p=>{
Promise.resolve(p).then(resolve).catch(reject);
})
})
}
Promise.allSettled
Promise.allSettled传入一个iterable的对象,返回一个在给定的promise都已经fulfilled或rejected后的promise结果对象。
Promise.allSettled = function(promises){
return new Promise((resolve,reject)=>{
let result = [];
let count = 0;
let len = promises.length;
if(len === 0) resolve([])
promises.forEach((p,i)=>{
Promise.resolve(p).then(res=>{
count +=1;
result[i] = {
value:res,
status:"fullfilled"
};
if(count === len ) resolve(result)
}).catch(err){
result[i] = {
value:err,
status:"rejected"
};
if(count === len ) resolve(result)
}
})
})
}
Promise的错误捕获机制
问:Promise的错误捕获机制了解吗?
答:Promise通常使用catch来捕获
rejected状态的promise结果。如果没有使用catch来显示捕获错误,可以使用unhandlerejection为它兜底。try...catch不能捕获promise的错误。
Promise的错误捕获机制:
Promise.catch()捕获Promsie抛出的异常,但是写的顺序需要在抛出错误之后
- 浏览器提供
unhandledrejection事件为Promise异常提供了兜底
try {
// console.log(a)
Promise
.resolve('34')
.then(res => {
return new Error('报错2')// 状态是fullfiled
})
.catch((err) => {// 不会捕获到"报错1" 和 "报错2"
console.log("innerCatch", err)//但是如果有catch方法捕获,unhandledrejection就不会触发
})
.then(res => {
throw new Error('报错1')
})
} catch (err) {
console.log("err", err)// 不会触发这个
}
// 当没有Promise.catch()捕获到错误,就会触发兜底unhandledrejection
window.addEventListener("unhandledrejection", function (err) {
console.log('unhandledrejection', err)
})
其他场景面试题
手写并发的Promsie请求,有调度和限制数
class Scheduler {
constructor(maxCount){//可以传进限制并发数
this.maxCount = maxCount || 2;//默认为2
this.list = [];
this.curRun = 0;//当前并发数
}
addTask(promiseFn) {
// coding
this.list.push(promiseFn);
}
startTask(){
for(let i=0;i<this.maxCount;i++){
this.request();
}
}
request(){
if(!this.list || !this.list.length || this.curRun >= this.maxCount){
return;
}
this.curRun++;
// 执行promise请求
this.list.shift()().then(res=>{
this.curRun--;
this.request();
})
}
}
let wait = (time) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(), time);
});
let scheduler = new Scheduler(4);
let addTask = (time, value) => {
scheduler.addTask(() => wait(time).then(() => console.log(value)));
};
// test
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.startTask();
打印结果:
3
4
2
1
实现一个函数可以每隔1s打印出1,2,3
way1: Promise链式调用
// 链式调用
Promise.resolve()
.then(() => {
return new Promise((resolve, _) => {
setTimeout(() => {
console.log(1);
resolve();
}, 1000);
});
})
.then(() => {
return new Promise((resolve, _) => {
setTimeout(() => {
console.log(2);
resolve();
}, 1000);
});
})
.then(() => {
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(3);
}, 1000);
});
});
way2:Promise + reduce
//简单的写法 arr的reduce
let arr = [1, 2, 3];
arr.reduce((p, cur) => {
return p.then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(console.log(cur)), 1000);
});
});
}, Promise.resolve());
实现一个红黄绿灯交互重复亮的效果
比如红灯3s亮一次,黄灯2s亮一次,绿灯1s亮一次,三个灯不断交替重复亮灯
function red() {
console.log("red");
}
function yellow() {
console.log("yellow");
}
function green() {
console.log("green");
}
var light = function (cb, timer) {
return new Promise((resolve, _) => {
setTimeout(() => {
cb();
resolve();
}, timer);
});
};
function step() {
Promise.resolve()
.then(() => {
return light(red, 3000);
})
.then(() => {
return light(yellow, 2000);
})
.then(() => {
return light(green, 1000);
})
.then(() => {
return step();
});
}
step()