Promise.all和promise.allSettled的用法,promise.all的错误捕获方法

Promise.all

用于优化多个同时处理的异步请求,降低时间

例如

async function getPageData() {const user = await fetchUser()const product = await fetchProduct()
} 

在此函数中,我们依次等待获取用户数据和产品数据。

但是这两个相互之间不存在依赖依赖,所以我们不必等待一个完成再发出对下一个的请求。

相反,我们可以同时触发两个请求,并同时等待。这样优化此功能以在短短一半的时间。

async function getPageData() {const [user, product] = await Promise.all([fetchUser(), fetchProduct()])
} 

如果我们想象每个请求都需要 1 秒来响应,而在我们的原始函数中,我们会连续等待两个请求,总共需要 2 秒才能完成,在这个新函数中,我们同时等待两个请求,所以我们函数在 1 秒内完成——时间减半!

缺点

首先,我们在这里根本不处理错误。

所以你可以说“当然,我会把它放在一个大的 try-catch 块中”。

async function getPageData() {try {const [user, product] = await Promise.all([fetchUser(), fetchProduct()])} catch (err) {//  这会有一个大问题}
} 

但这其实有一个大问题。

假设fetchUser首先完成并出现错误。这将触发我们的 catch 块,然后继续执行该功能。

如果fetchProduct之后出错,这将不会触发 catch 块。那是因为我们的功能已经继续了。catch 代码已经运行,函数已经完成。

因此,这将导致未处理fetchProductpromise.reject

解决方法1

解决上述问题的一种方法是将函数传递给.catch(),例如:

// 用户捕获错误自定义去处理
function handle(err) {alertToUser(err) saveToLoggingService(err)
}

function onReject(err) {handle(err)return err
}

async function getPageData() {const [user, product] = await Promise.all([fetchUser().catch(onReject), // ⬅️fetchProduct().catch(onReject) // ⬅️])if (user instanceof Error) {handle(user) // ✅}if (product instanceof Error) {handle(product) // ✅}
} 

在这种情况下,如果我们得到一个错误,我们返回处理错误和它本身。所以现在我们的结果userproduct对象要么是一个Error,我们可以检查它instanceof,要么是我们没有报错,正确的结果。

这还不错,解决了我们之前的问题。

但是,这里的主要缺点是我们需要确保我们始终在异步请求后跟着.catch(onReject)。遗憾的是,这很容易被遗漏,而且也不是最容易为其编写防弹 eslint 规则的方法。

解决方法2

我们并不总是需要在创建Promise后立即await。另一种几乎相同的技术是:

**同步请求、异步等待 **

async function getPageData() {// 同时触发两个请求const userPromise = fetchUser().catch(onReject)const productPromise = fetchProduct().catch(onReject)const user = await userPromiseconst product = await productPromise// 处理错误if (user instanceof Error) {handle(user)}if (product instanceof Error) {handle(product)}
} 

因为我们在等待任何一个之前触发了每个请求,所以这个版本与我们上面使用Promise.all.

此外,在这种格式中,try/catch如果我们愿意,我们可以安全地使用而不会出现我们之前遇到的问题:

async function getPageData() {const userPromise = fetchUser().catch(onReject)const productPromise = fetchProduct().catch(onReject)// Try/catch eachtry {const user = await userPromise} catch (err) {handle(err)}try {const product = await productPromise} catch (err) {handle(err)}
} 

在这三者之间,我个人比较喜欢这个Promise.all版本,因为“这两个东西一起等”感觉更地道。但话虽如此,我认为这只是归结为个人喜好

解决方案 3

Promise.allSettled

Promise.allSettled

JavaScript 中内置的另一种解决方案是使用Promise.allSettled.

Promise.allSettled,我们得到一个包含每个承诺结果的值或错误的结果对象。

async function getPageData() {const [userResult, productResult] = await Promise.allSettled([fetchUser(), fetchProduct()])
} 

结果对象有 3 个属性:

  • status-"fulfilled"要么"rejected"
  • value- 仅在 status === 'fulfilled'时出现。Promise 被 resolve 的 value
  • reason- 仅在 status === 'rejected' 时出现。Promise 被 reject 的 reson。

所以我们现在可以读取每个承诺的状态,并单独处理每个错误,而不会丢失任何关键信息:

async function getPageData() {// 同时触发两个请求const [userResult, productResult] = await Promise.allSettled([fetchUser(), fetchProduct()])//userif (userResult.status === 'rejected') {const err = userResult.reasonhandle(err)} else {const user = userResult.value}//productif (productResult.status === 'rejected') {const err = productResult.reasonhandle(err)} else {const product = productResult.value}
} 

但是,这是很多重复代码。那么让我们抽象一下:

async function getPageData() {const results = await Promise.allSettled([fetchUser(), fetchProduct()])// 更美观const [user, product] = handleResults(results)
} 

我们可以像这样实现一个简单的handleResults功能:

// 如果发生任何错误 则抛出泛型函数,或返回响应
// 如果没有错误发生
function handleResults(results) {const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)if (errors.length) {// 将所有错误聚合为一个throw new AggregateError(errors)}return results.map(result => result.value)
} 

我们可以在这里使用一个巧妙的技巧,即AggergateError类,来抛出一个可能包含多个内部的错误。这样,当被捕获时,我们会通过.errorsan 上的属性获得包含所有详细信息的单个错误AggregateError,其中包括所有错误:

async function getPageData() {const results = await Promise.allSettled([fetchUser(), fetchProduct()])try {const [user, product] = handleResults(results)} catch (err) {for (const error of err.errors) {handle(error)}}
} 

后记

项目实际过程中,如果需要捕获每个请求的错误,可以在axios等响应拦截器中判断响应代码是否正确,这样就可以拦截所有错误的请求。 但是如果想在每个页面单独自定义捕获信息就可以用如上方法。 两者并不冲突

总结

  • 如果Promise.all请求需要捕获每个错误,就用Promise.allSettled
  • 确保我们避免混淆——我想指出,重要的是要注意,当我们在这里谈论并发时,我们指的是并发等待Promise,而不是并发执行代码。
  • 避免过早优化,并确保在增加更多复杂性之前有充分的理由。速度快固然好,但在盲目并发代码中的所有内容之前考虑是否需要它。
  • JavaScript 中的 Promise 非常强大,虽然在过度地将顺序异步/等待转换为并发等待之前应该谨慎行事,但 JavaScript 内置了许多有用的工具,可帮助您在需要时加快速度,这些值得了解。

完全体代码

function handle(err) {alertToUser(err)saveToLoggingService(err)
}

function handleResults(results) {const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)if (errors.length) { throw new AggregateError(errors)}return results.map(result => result.value)
}

async function getPageData() {const results = await Promise.allSettled([fetchUser(), fetchProduct()])try {const [user, product] = handleResults(results)} catch (err) {for (const error of err.errors) {handle(error)}}
} 

谢谢!

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

你可能感兴趣的:(前端,javascript,开发语言)