前端高频面试题之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

相关推荐
IT_陈寒2 小时前
JavaScript 开发者必知的 7 个 ES2023 新特性,第5个能让代码量减少50%
前端·人工智能·后端
李少兄2 小时前
前端开发中的 CSS @keyframes 动画指南
前端·css
LYFlied2 小时前
前端技术风险防控:以防为主,防控结合
前端·工程化·技术风险防控
阿蒙Amon2 小时前
JavaScript学习笔记:8.日期和时间
javascript·笔记·学习
宁雨桥2 小时前
前端跨页面通信:从基础到工程化的全面指南
前端·vue.js·react.js
梵尔纳多2 小时前
electron 安装
前端·javascript·electron
心.c2 小时前
初步了解Next.js
开发语言·前端·javascript·js
挫折常伴左右2 小时前
初见HTML
前端·html