关于Promise的面试题

94 阅读5分钟

前言

面试官:了解Promise吗?

我:blabla...

面试官:Promise.all的执行顺序是什么?

我:懵...它不是哪个resolved快,然后存到结果数组嘛???

...

面试官:Promise.all实际上没有执行顺序,它是并发执行的。

我:✨🎉🎊🎃🎫🎨🎀🎆🎇 (原来是这样,尝试表达我的无知...)

所以,打算开始看一下Promise的面试题,开始总结...

Promise

问:对Promise的理解,为什么要有Promise?

答:Promise译作“承诺”,是ES6中异步编程的解决方案之一,它的提出是为了解决传统ajax请求存在的回调地狱问题,Promise的链式调用降低了代码的复杂度、提高代码的可读性。

Promise有三种状态:

  • pending:进行中
  • fulfilled:已成功
  • rejected:已失败

特点:

  • 对象状态不受外界的影响;
  • 一旦状态改变(pending变成fulfilledpending变成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都已经fulfilledrejected后的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()