1

图怪兽_663d0134aa3c91e765cbce0374fb5ee0_24853.png

前言

Promise作为异步编程的一种解决方案,无论什么阶段的前端都是需要牢牢掌握,在面试中也是属于必问的。

一、高频概念问答

1、你理解的Promise?

<details>

<summary><font color=blue>参考</font></summary> 

Promise是ES6中异步编程的一种解决方案;它是为了解决异步链式调用出现的回调地狱问题;有三种状态pending进行中、fulfilled成功、rejected失败,且状态一旦改变不可逆;promise.then()是微任务
</details>

2、Promise常用方法?

<details>

<summary><font color=blue>参考</font></summary> 

Promise.all、Promise.allSettled、Promise.any、Promise.race、Promise.resolve、Promise.reject
</details>

3、你理解的async和await?

<details>

<summary><font color=blue>参考</font></summary> 

async是ES7中对于异步操作的解决方案,它是Generator函数的语法糖,解决了promise链式调用的问题,用同步的方式去执行异步;
</details>

4、Promise.all,Promise.race区别

<details>

<summary><font color=blue>参考</font></summary> 

Promise.all() 全部promise成功才算成功,一个promise就算失败,返回成功的数据数组,失败抛出最先失败的promise的reason。 Promise.race() 最先的promise完成则返回,promise结果和最先完成的promise一致
</details>

5、执行顺序?

难度 ⭐⭐⭐

    1. 执行宏任务的过程中,遇到微任务,依次加入微任务队列;
    1. 当某个宏任务执行完后,会查看是否有微任务队列;
    2. 有,执行微任务队列中的所有任务;
    3. 没有,读取宏任务队列中最前面任务;
    1. 栈空后,再次读取微任务队列里的任务,依次类推
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2() {
    console.log('async2');
}

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});

console.log('script end');

// 输出结果

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

难度 ⭐⭐⭐⭐

// 在上题中做了一点改动

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
    });
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise3');
    resolve();
}).then(function() {
    console.log('promise4');
});

console.log('script end');

// 输出结果
script start
async1 start
promise1
promise3
script end
promise2
async1 end
promis4
setTimeout

难度 ⭐⭐⭐⭐

console.log('start')
 setTimeout(()=>{
    console.log("children2")
    Promise.resolve().then(()=>{
        console.log("children3")
    })
 },0)
 
 new Promise(function(resolve,reject){
     console.log('children4')
     setTimeout(()=>{ //重点
        console.log("children5")
        resolve("children6")
     },0 )
 }).then(res=>{
     console.log("children7")
     setTimeout(()=>{
        console.log(res)
     },0)
 })
 
 // 输出结果
 start
 children4
 //第一轮宏任务结束 尝试清空微任务队列 没有微任务
 children2
 //第二轮宏任务结束 尝试清空微任务队列 
 children3
 //第三轮
 children5
 children7
 children6

二、手撕代码

手写 Promise 和 Promise.all 是面试频率最高的;

1、Promise.all 难度⭐⭐⭐

列举几个关键点:
  • 1.return Promise 返回也是Promise
  • 2.参数类型判断,传入的参数必须是数组;
  • 3.数组元素类型,判断传入的值是否是promise,使用Promise.resolve 方法会把参数转化为promise;
  • 4.返回结果顺序问题;

    function PromiseAll(promiseArray){
      return new Promise((resolve,reject)=>{
          // !!! 考点1
          if(!Array.isArray(promiseArray)){
              return reject(new Error('传入的参数必须是数组'))
          }
          const result = []
          let counter = 0
          const promisenNums = promiseArray.length
          
          // !!! 考点2 判断传入的值是否是promise
          //Object.prototype.toString.call==='[object Promise]'
          
          for(let i = 0 ;i < promisenNums ; i++){
              //Promise.resolve 方法会把参数转化为promise
              Promise.resolve(promiseArray[i]).then(value=>{
                  // push会造成数据错乱 用count计数,不用长度判断
                  // result.push(value)
                  // if(result.length === promisenNums){
                  //     resolve(result)
                  // }
                  //!!! 考点3 promise的顺序问题上面的方式忽略了promise的顺序问题
                  
                  counter++;
                  result[i] = value;
                  if(counter === promisenNums){
                      resolve(result)
                  }
              }).catch(e=>reject(e))
          }
      })
    }

    2、Promise 难度⭐⭐⭐⭐

    推荐文章 硬核! 手把手教你写A+规范的Promise

    class Promose {
      constructor(executor){
          this.status = 'pending'
          this.value = null 
          this.reason = null
          
          this.onResolvedCallbacks = [] //存放成功的回调函数 新加
          this.onRejectedCallbacks = [] //存放失败的回调函数 新加
    
    
          //reslove函数
          const resolve = (value) => {
              if(this.status === 'pending'){
                   this.status = 'fulfilled'
                   this.value = value 
    
                   //执行所有的订阅的fulfilled函数 新加
                  this.onResolvedCallbacks.forEach(fn=>fn())
              }
          }
          //reject函数
          const reject = (reason) => {
              if(this.status === 'pending'){
                  this.status = 'rejected'
                  this.reason = reason
    
                  //执行所有的订阅的rejected函数
                  this.onRejectedCallbacks.forEach(fn=>fn())
              }
          }
          try{
              executor(resolve,reject)
          }catch(err){
              reject(err)
          }
      }
    
      then(onFulfilled,onRejected){
          //成功的回调
          if(this.status = 'fulfilled'){
              onFulfilled(this.value)
          }
          //失败的回调
          if(this.status = 'rejected'){
              onRejected(this.reason)
          }
    
          //如果是等待状态,就把res,rej的处理回调函数放入对应的队列 新加
          if(this.status ==='pending'){
              //放入成功回调队列
              this.onResolvedCallbacks.push(()=>{
                  //写额外的逻辑
                  onFulfilled(this.value)
              })
    
              //放入失败回调队列
              this.onRejectedCallbacks.push(()=>{
                  //写额外的逻辑
                  onRejected(this.reason)
              })
          }
      }
    }

3、Promise.allSettled 难度⭐⭐⭐

列举几个关键点:
  • 1.return Promise 返回也是Promise
  • 2.参数类型判断,传入的参数必须是数组;
  • 3.数组元素类型,判断传入的值是否是promise,使用Promise.resolve 方法会把参数转化为promise;
  • 4.返回结果顺序问题;
  • 5.对每个对象,都有status,如果值为 fulfilled,则上存在一个 value 。如果值为 rejected,则存在一个 reason

    function allSettled(promises) {
      return new Promise((resolve, reject) => {
          // 参数校验
          if (Array.isArray(promises)) {
              let result = []; // 存储结果
              let count = 0; // 计数器
    
              // 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
              if (promises.length === 0) return resolve(promises);
    
              promises.forEach((item, index) => {
                  // 非promise值,通过Promise.resolve转换为promise进行统一处理
                  myPromise.resolve(item).then(
                      value => {
                          count++;
                          // 对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。
                          result[index] = {
                              status: 'fulfilled',
                              value
                          }
                          // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
                          count === promises.length && resolve(result);
                      },
                      reason => {
                          count++;
                          /**
                            * 对于每个结果对象,都有一个 status 字符串。如果值为 rejected,则存在一个 reason 。
                             * value(或 reason )反映了每个 promise 决议(或拒绝)的值。
                             */
                          result[index] = {
                              status: 'rejected',
                              reason
                          }
                          // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
                          count === promises.length && resolve(result);
                      }
                  )
              })
          } else {
              return reject(new TypeError('Argument is not iterable'))
          }
      })
    }
    

4、Promise.race 难度⭐⭐⭐

function race(promises) {
  return new myPromise((resolve, reject) => {
    // 参数校验
    if (Array.isArray(promises)) {
      // 如果传入的迭代promises是空的,则返回的 promise 将永远等待。
      if (promises.length > 0) {
        promises.forEach((item) => {
          /**
           * 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,
           * 则 Promise.race 将解析为迭代中找到的第一个值。
           */
          myPromise.resolve(item).then(resolve, reject);
        });
      }
    } else {
      return reject(new TypeError("Argument is not iterable"));
    }
  });
}

5、Promise.resolve 难度⭐⭐

Promose.resolve = function (value) {
  // 如果这个值是一个 promise ,那么将返回这个 promise
  if (value instanceof myPromise) {
    return value;
  } else if (value instanceof Object && "then" in value) {
    // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;
    return new myPromise((resolve, reject) => {
      value.then(resolve, reject);
    });
  }

  // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
  return new myPromise((resolve) => {
    resolve(value);
  });
};

6、Promise.reject 难度⭐⭐

/** 
* myPromise.reject 
* @param {*} reason 表示Promise被拒绝的原因 
*/
Promose.reject = function (reason) {
  return new myPromise((resolve, reject) => {
    reject(reason);
  });
};

7、Promise.finally 难度⭐⭐

class myPromise {
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  /**
   * finally
   * @param {*} callBack 无论结果是fulfilled或者是rejected,都会执行的回调函数
   * @returns
   */
  finally(callBack) {
    return this.then(callBack, callBack);
  }
}

三、大厂真题

1、使用Promise实现每隔1秒输出1,2,3,4?

const arr = [1, 2, 3 , 4]
arr.reduce((p, x) => {
  return p.then(() => {
    return new Promise(r => {
      setTimeout(() => r(console.log(x)), 1000)
    })
  })
}, Promise.resolve())

2、Promise做缓存?

场景:一个频繁调用接口的方法在多处使用


const cacheMap = new Map(); //用一个Map来存

function enableCache(target, name, descriptor) {
    const oldValue = descriptor.value; //拿到原来的方法

    descriptor.value = function(...args) { //重写这个方法
        //因为请求的参数肯定会不一样,所以要定义一个的key
        const cacheKey = name + JSON.stringify(args);
        if (!cacheMap.get(cacheKey)) {
            //为什么要加catch 因为你的请求有可能会报错
            const cacheValue = Promise.resolve(oldValue.apply(this, args)).catch(_ => {
                cacheMap.set(cacheKey, null) //报错了,设置成null
            })
            cacheMap.set(cacheKey, cacheValue)
        }
        return cacheMap.get(cacheKey); //把缓存拿出来即可,无须再去http请求,无须再去请求服务端
    }
    return descriptor;
}

class PromiseClass {
   //装饰器
   @enableCache
   static async getInfo() {}
}

// 使用
PromiseClass.getInfo()
PromiseClass.getInfo()
PromiseClass.getInfo()

3、用Generator实现async和await

function* fn() {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("a");
            resolve("resolve1");
        }, 500);
    });
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("b");
            resolve("resolve2");
        }, 500);
    });
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("c");
            resolve("resolve3");
        }, 500);
    });
}
co(fn);

function co(fn) {
    let f = fn();
    next();

    function next(data) {
        let result = f.next();
        if (!result.done) {
            // 上一个异步走完了再执行下一个异步
            result.value.then((Info) => {
                console.log(Info, data);
                next(Info);
            })
        }
    }
}
>a
>resolve1 undefined
>b
>resolve2 resolve1
>c
>resolve3 resolve2

❤️感谢阅读

  1. 如果本文对你有帮助,不要吝啬你的赞哟,你的「赞」是我前行的动力;
  2. 欢迎关注公众号  【冥想侃前端】  一起学习进步。

冥想侃前端
16 声望0 粉丝