前端高频面试题之Promise相关方法

前言

Promise一直是前端面试中的热点,下面给大家介绍下Promise的相关方法

1. Promise.all

1.1 介绍

调用Promise.all时需要传入一个promise数组,我们称它为promiseArr,然后Promise.all会返回一个新的Promise,我们把它称为p

  • 如果promiseArr中有一个Promise失败,则走入pcatch回调中,并拿到对应的错误对象
  • 如果promiseArr里面全成功,则会走入pthen方法中,并能拿到之前传入的promiseArr对应的promise结果

1.2 原理

js 复制代码
function promiseAll(promiseArray) {
    return new Promise((resolve, reject) => {
        if(!Array.isArray(promiseArray)) {
            return reject(new Error('传入的参数必须是数组'))
        }
        const promiseNum = promiseArray.length
        const results = new Array(promiseNum)
        let count = 0
        promiseArray.forEach((promise, index) => {
            Promise.resolve(promise).then(res => {
                results[index] = res
                if(++count === promiseNum) {
                    resolve(results)
                }
            })
            .catch(reject)
        })
    })
}

注意点:

  • 需要默认返回一个promise
  • 存结果的results数组需要通过index和传入的promise数组一一对应

1.3 应用场景

  • 并行发送多个请求。
  • 多个文件的并行读取。
  • 并行执行多个任务。

2. Promise.race

2.1 介绍

race的意思是赛跑,哪个Promise跑的快,也就是哪个Promise最先成功或失败,整个Promise.race也就对应的成功或失败。

2.2 原理

js 复制代码
function PromiseRace(promiseArray) {
    return new Promise((resolve, reject) => {
        if(!Array.isArray(promiseArray)) {
            return reject(new Error('传入的参数必须是数组'))
        }
        promiseArray.forEach((promise, index) => {
            Promise.resolve(promise).then(res => {
                resolve(res)
            })
            .catch(reject)
        })
    })
}

2.3 应用场景

  • 尽快拿到请求结果 :比如多个接口都可以拿到你想请求的数据,你就可以用Promise.race,如果拿到了一个请求的响应结果,你就可以直接渲染页面了,这样能加快我们的页面响应速度。
  • 多个资源加载 :你可以把手动去加载多个资源,然后用Promise.race来等待最快加载的资源后采取行动,以提高加载性能。
  • 竞态条件处理:同时尝试多个可能的解决方案,并采取第一个可用的解决方案。

3. Promise.prototype.finally

3.1 介绍

finallythencatch一样,都是Promise原型上的方法,与thenreject一同,不管Promise成功还是失败,最终都会执行finally方法。

3.2 原理

js 复制代码
Promise.prototype.finally = function (cb) {
  return this.then(
    (y) => {
      return Promise.resolve(cb()).then(() => y);
    },
    (r) => {
      return Promise.resolve(cb()).then(() => {
        throw r;
      }); 
    }
  );
};

3.3 应用场景

  1. 清理资源或状态 :比如你需要在最Promise执行结束之后释放一些资源,比如打开的文件数据库连接网络连接等,或者释放状态,比如页面的loading
  2. 执行收尾操作 :比如无论Promise成功或者失败,你都要执行一些收尾工作,比如记录日志、发送统计信息或触发一些事件等。
  3. 统一处理 :在thencatch的时候都需要进行的处理,这时候你就不需要写两次重复的代码了,直接放在finally当中。

4. Promise.allSettled

4.1 介绍

  1. Promise.allSettled()方法接收一组Promise作为参数,返回一个新的Promise实例
  2. 只有等到所有的这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束
  3. 返回新的Promise实例,一旦结束,状态总是fulfilled,不会变成rejected
  4. 新的promise实例给监听函数传递一个数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled的Promise实例。每个对象都有status属性,对应着fulfilled和rejected。fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。
  5. 当我们不需要关心异步操作的结果,只会关心这些操作有没有结束的时候,这时候Promise.allSettled就派上用场了。

4.2 原理

js 复制代码
const formatSettledResult = (isSuccess, value) => {
  return isSuccess ? ({ status: 'fulfilled', value }) : ({ status: 'rejected', reason: value })
}

Promise.all_settled = function (promises) {
  if (!Array.isArray(promises)) {
    throw new Error('传入的参数必须是数组y')
  }
  let num = 0, len = promises.length, results = Array(len)

  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then((value) => {
        results[index] = formatSettledResult(true, value)
        if (++num === len) {
          resolve(results)
        }
      })
      .catch((reason) => {
        results[index] = formatSettledResult(false, reason)
        if (++num === len) {
          resolve(results)
        }
      })
    })
  })
}

4.3 应用场景

  1. 并行请求:当我们需要并行发送多个请求,并在所有请求完成后获得每个请求的结果和状态信息
  2. 批量处理:在等待所有的请求完成后,对各个请求的状态分别进行处理。
  3. 处理多个资源加载:可以将资源的加载封装为promise,并同时加载多个资源,并在加载完后根据promise的状态对每个资源进行对应的处理。

5. Promise.cache

5.1 介绍

利用promise实例化立即执行的特性可以做请求的缓存。

5.2 原理

js 复制代码
const cacheMap = new Map()
function enableCache(target, name, descriptor) {
    const val = descriptor.value
    descriptor.value = async function (...args) {
        const cacheKey = name + JSON.stringify(args)
        if (!cacheMap.get(cacheKey)) {
            const cacheValue = Promise.resolve(val.apply(this, args)).catch(() => {
                cacheMap.set(cacheKey, null)
            })
            cacheMap.set(cacheKey, cacheValue)
        }
        return cacheMap.get(cacheKey)
    }
    return descriptor
}

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

    }
}
PromiseClass.getInfo()
PromiseClass.getInfo()
PromiseClass.getInfo()

这里我先定义一个Map作为缓存对象,然后用方法名,也就是上面的getInfo + 参数序列化后的值作为缓存key值,然后就可以实现请求的缓存了,当然我这里缓存key设置的比较简单,实际业务场景肯定会更为严谨一些,然后业务中如果用了缓存的话,需要考虑缓存失效的问题,过快失效和过久不失效都可能会让程序出现bug,需要注意一下。

5.3 应用场景

  • 缓存接口请求结果,避免重复请求。
  • 也可以用来缓存复杂函数计算结果,提高性能。

6. Promise.limit

6.1 介绍

Promise.limit可以通过promise实现并发控制

6.2 原理

js 复制代码
function limitLoad(promiseArray, limit) {
    const results = [];
    const promises = promiseArray.slice(0, limit).map((promise, index) => {
        return promise.then((value) => {
            results[index] =  value;
            return index;
        })
    })
    let p = Promise.race(promises)
    for(let i = limit; i < promiseArray.length; i++) {
        p = p.then((index) => {
            promises[index] = promiseArray[i].then((value) => {
                results[i] = value;
                return value
            })
            return Promise.race(promises)
        })
    }
    return p.then(() => results);
}

首先,声明一个results数组存储promise结果,然后先取出limit个数的promise,通过Promise.race可以拿到最快执行完的那一个,我们前面会把每个promise对应在promises数组的位置index往下传递,然后通过循环串成一个promise链,在Promise.race的then方法中,通过前面的index找到那个最快执行完的promise所在的位置,将其替换,最终promise链执行完将存储promise结果results数组返回就行了。

6.3 应用场景

  1. 并发请求控制 :有时候为避免服务器性能问题,可以使用Promise.limit进行并发控制,以提高请求的响应速度
  2. 批量处理限制 :当对大量数据进行处理时,有时候一次性处理会导致服务器内存溢出,这时可以采用Promise.limit控制同时处理的数据数量,以提高资源的处理效率。
  3. 队列调度 :在任务调度和队列管理中,可以使用Promise.limit将所有任务放在固定大小的任务池中,并限制同时执行的任务数量,这样可以确保任务按照限制的并发度进行顺序执行,避免资源竞争和过度负载

7. Promise.try

7.1 介绍

Promise.try可以把一个函数包装为一个 Promise。

js 复制代码
function test(a, b) {
  console.log('调用test', a, b)
  return 'test res';
}

Promise.try(test, 'a', 'b').then((res) => {
  console.log('then', res)
}).catch(() => {
  console.log('catch');
})

/**
 * 打印结果:
 * 调用test a b
 * then test res
 */

它也支持传入异步函数,比如 async 函数。

js 复制代码
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111);
  }, 3000)
})

async function test(a, b) {
  await p;
  console.log('test', a, b)
  return 'test res';
}

Promise.try(test, 'a', 'b').then((res) => {
  console.log('then', res)
}).catch(() => {
  console.log('catch');
})

/**
 * 先等待3s,然后打印结果如下:
 * 调用test a b
 * then test res
 */

7.2 原理

js 复制代码
Promise.try = function (fn, ...args) {
  return new Promise((resolve, reject) => {
    try {
      resolve(fn(...args))
    } catch (e) {
      reject(e)
    }
  })
}

Promise.try 的函数签名为 Promise.try(func, arg1, arg2, ...argN) ,它会把 arg1, arg2, ...argN 作为参数传递给 func, 并以同步的方式立即执行 func 函数,最后将其结果包装为一个 Promise 对象。

7.3 应用场景

  • 它可以把一个函数包装为一个 Promise 对象,比 new Promise((resolve) => resolve(func())) 更加简洁。

8. Promise.withResolvers

8.1 介绍

Promise.withResolvers 函数经调用会返回一个新的 Promise,以及 resolvereject 方法。

js 复制代码
const { promise, resolve, reject } = Promise.withResolvers()

8.2 原理

js 复制代码
Promise.withResolvers = function () {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

8.3 应用场景

  • 可以替代 new Promise 创建 Promise 对象,使得 promiseresolvereject 三个变量在同一个作用域中,方便管理。

小结

以上给大家介绍了8个 Promise 相关方法以及各自应用场景,其中需要注意 Promise.tryPromise.withResolvers 属于比较新的 API ,使用时需注意其兼容性。

如果还有兴趣想掌握手写Promise 的话,可以移步我之前写的文章,前端高频面试题之手写Promise

相关推荐
林小帅28 分钟前
【笔记】OpenClaw 架构浅析
前端·agent
林小帅1 小时前
【笔记】OpenClaw 生态系统的多语言实现对比分析
前端·agent
程序猿的程1 小时前
开源一个 React 股票 K 线图组件,传个股票代码就能画图
前端·javascript
不爱说话郭德纲2 小时前
告别漫长的HbuilderX云打包排队!uni-app x 安卓本地打包保姆级教程(附白屏、包体积过大排坑指南)
android·前端·uni-app
大雨还洅下2 小时前
前端JS: 虚拟dom是什么? 原理? 优缺点?
javascript
唐叔在学习2 小时前
[前端特效] 左滑显示按钮的实现介绍
前端·javascript
用户5282290301802 小时前
【学习笔记】ECMAScript 词法环境全解析
前端
青青家的小灰灰2 小时前
React 架构进阶:自定义 Hooks 的高级设计模式与最佳实践
前端·react.js·前端框架
Angelial3 小时前
Vite 性能瓶颈排查标准流程
前端
不要秃头啊3 小时前
别再谈提效了:AI 时代的开发范式本质变了
前端·后端·程序员