使用Async/Await和Promise.all()高效聚合循环内异步数据

使用Async/Await和Promise.all()高效聚合循环内异步数据

本教程旨在解决JavaScript中在循环内调用返回Promise的异步函数时,如何高效地收集所有异步操作的结果并进行统一处理的问题。我们将重点介绍如何利用async/await语法结合Promise.all()方法,简化异步代码逻辑,确保所有异步任务完成后,能够准确获取并聚合所需数据,从而避免常见的异步编程陷阱。

在现代javascript应用开发中,处理异步操作是家常便饭。一个常见的场景是,我们需要从一个异步源获取数据列表,然后对列表中的每个项执行另一个异步操作,并最终将所有这些异步操作的结果聚合起来。例如,从一个api获取订单列表,然后为每个订单的买家获取其邮箱地址,并将所有邮箱地址收集到一个列表中。直接在循环中使用promise的.then()方法进行聚合,往往会导致数据不完整或逻辑错误,因为循环是同步执行的,而.then()回调是异步的。

理解传统Promise链式调用的局限性

考虑以下场景:您有一个订单列表,需要为每个订单的买家查询其邮箱。myListOrdersFunction()和myGetMemberFunction()都是返回Promise的异步函数。

原始尝试的代码可能如下所示:

const myListOrdersFunction = require('backend/test').myListOrdersFunction; const myGetMemberFunction = require('backend/getMember').myGetMemberFunction;  $w.onReady(function () {     const emailListPromise = myListOrdersFunction()         .then(listedOrders => {             const loginEmails = [];             for (const order of listedOrders) {                 myGetMemberFunction(order.buyer.memberId)                     .then(member => {                         // 这个回调是异步执行的,可能在循环结束后才被调用                         loginEmails.push(member.loginEmail);                     })                     .catch(error => {                         console.log(error);                     });             }             // 问题所在:此时 loginEmails 可能为空或不完整,因为内部的then还在等待             return loginEmails;          })         .catch(error => {             console.log(error);         });      // 此时 emailListPromise 内部的 loginEmails 尚未完全填充     const printEmails = async () => {         const a = await emailListPromise;         console.log("a ",a); // 很可能打印出空数组 []     }     printEmails(); });

上述代码的问题在于,for…of循环是同步执行的。在循环内部调用myGetMemberFunction().then(…)会立即返回一个Promise,但.then()中的回调函数(loginEmails.push(…))是异步的,它会在未来的某个时间点执行。因此,当return loginEmails;语句执行时,loginEmails数组很可能还没有被完全填充,导致最终获取到的是一个空数组或不完整的数据。

Async/Await与Promise.all():现代异步编程利器

为了优雅且高效地解决这类问题,JavaScript提供了async/await语法糖和Promise.all()方法,它们是处理复杂异步流程的强大组合。

  • async/await:它允许我们以同步的方式编写异步代码,使得代码更易读、更易维护。async函数会返回一个Promise,而await关键字只能在async函数内部使用,它会暂停async函数的执行,直到其后的Promise解决(resolved)并返回结果。
  • Promise.all():这个方法接收一个Promise数组作为输入,并返回一个新的Promise。当输入数组中的所有Promise都成功解决时,Promise.all()返回的Promise也会解决,其结果是一个包含所有输入Promise解决值的数组,且顺序与输入Promise的顺序一致。如果输入数组中的任何一个Promise被拒绝(rejected),则Promise.all()返回的Promise也会立即被拒绝,并返回第一个被拒绝的Promise的错误信息。

将这两者结合,我们可以实现对多个并行异步操作结果的等待和聚合。

解决方案:重构代码

使用async/await和Promise.all()重构上述问题,代码将变得简洁且逻辑清晰:

const myListOrdersFunction = require('backend/test').myListOrdersFunction; const myGetMemberFunction = require('backend/getMember').myGetMemberFunction;  // 假设此代码运行在一个支持async/await的环境中,例如Wix的$w.onReady函数 $w.onReady(async function () {     try {         // 1. 获取订单列表         // await会等待myListOrdersFunction返回的Promise解决,并获取其结果         const listedOrders = await myListOrdersFunction();          // 2. 为每个订单的买家获取成员信息         // 使用map方法将每个订单转换为一个myGetMemberFunction调用的Promise         // 此时,所有的myGetMemberFunction调用都已发起,并返回了对应的Promise         const memberPromises = listedOrders.map(order =>              myGetMemberFunction(order.buyer.memberId)         );          // 3. 并行等待所有成员信息获取完毕         // Promise.all会等待memberPromises数组中所有Promise都解决         const members = await Promise.all(memberPromises);          // 4. 从已获取的成员信息中提取所需的邮箱地址         const loginEmails = members.map(member => member.loginEmail);          // 至此,loginEmails数组已包含所有成员的邮箱地址         console.log("所有登录邮箱:", loginEmails);          // 在实际应用中,你可以在这里将loginEmails发送回API响应         // 例如:return ok({ "emails": loginEmails });      } catch (error) {         // 捕获任何异步操作中可能发生的错误         console.error("处理订单和成员信息时发生错误:", error);         // 在API场景中,可以返回一个错误响应         // 例如:return serverError(error);     } });

代码解析

上述重构后的代码通过以下步骤高效地解决了问题:

使用Async/Await和Promise.all()高效聚合循环内异步数据

聚好用AI

可免费AI绘图、AI音乐、AI视频创作,聚集全球顶级AI,一站式创意平台

使用Async/Await和Promise.all()高效聚合循环内异步数据115

查看详情 使用Async/Await和Promise.all()高效聚合循环内异步数据

  1. 初始化获取订单: const listedOrders = await myListOrdersFunction();myListOrdersFunction()返回一个Promise,await关键字会暂停async函数的执行,直到这个Promise解决,并将其结果(listedOrders)赋值给变量。这是整个流程的起点,确保我们首先获取到所有订单数据。

  2. 生成成员信息Promise数组: const memberPromises = listedOrders.map(order => myGetMemberFunction(order.buyer.memberId)); 对于从myListOrdersFunction获取的每个订单(listedOrders),我们使用map方法创建一个新的数组。这个新数组的每个元素都是调用myGetMemberFunction(order.buyer.memberId)后返回的一个Promise。重要的是,此时这些Promise已经开始执行,但我们还没有等待它们的结果,这使得它们可以并行执行。

  3. 并行等待所有Promise解决: const members = await Promise.all(memberPromises);Promise.all()接收memberPromises数组,并返回一个新的Promise。await关键字会等待这个新的Promise解决。这意味着,它会等待memberPromises数组中所有的myGetMemberFunction调用都完成并返回各自的结果。 一旦Promise.all()解决,members变量将是一个数组,其中包含了所有成功获取的成员对象,且顺序与原始listedOrders的顺序相对应。

  4. 提取所需数据: const loginEmails = members.map(member => member.loginEmail); 最后,我们再次使用map方法遍历members数组,从每个成员对象中提取loginEmail属性,从而得到最终的loginEmails列表。至此,loginEmails数组中包含了所有订单买家的邮箱地址,且数据是完整的。

  5. 错误处理: 整个逻辑被包裹在try…catch块中。这样,如果myListOrdersFunction()或Promise.all()中的任何一个Promise被拒绝(即发生错误),catch块将捕获到这个错误,从而允许我们进行适当的错误处理,例如记录错误或返回一个错误响应。

注意事项与最佳实践

在实际开发中,除了上述核心解决方案,还需要考虑以下几点:

  • 健壮的错误处理:虽然try…catch可以捕获整个async函数中的错误,但对于Promise.all(),如果其中任何一个Promise失败,整个Promise.all()都会立即拒绝。如果希望即使部分Promise失败也能获取到其他成功Promise的结果,可以考虑使用Promise.allSettled()。Promise.allSettled()会等待所有Promise都解决(无论成功或失败),并返回一个包含每个Promise状态和结果(或拒绝原因)的对象数组,这在某些场景下提供了更高的灵活性。

  • 性能考量:Promise.all()非常适合处理相互之间没有依赖关系的并行异步操作,因为它能最大化利用I/O并发,显著提高执行效率。如果异步操作之间存在顺序依赖(例如,第二个操作需要第一个操作的结果才能开始),则需要按顺序使用await。

  • 上下文环境:本示例是在Wix的$w.onReady函数中使用的,但async/await和Promise.all()是标准的JavaScript特性,适用于任何支持ES2017+的环境,例如Node.js后端服务或现代浏览器

  • 资源限制:当需要处理的Promise数量非常大时(例如成千上万个),直接使用Promise.all()可能会导致一次性发起过多的请求,从而耗尽系统资源或触发API的速率限制。在这种情况下,可能需要实现一个批处理或限流机制,例如使用自定义的Promise池来控制并发度。

总结

通过将async/await与Promise.all()结合使用,我们能够将复杂的异步数据聚合逻辑转化为直观、易读的同步风格代码。这种模式不仅提高了代码的可维护性,还确保了在处理循环内的多个异步操作时,能够高效且准确地收集所有所需的结果,从而构建出更加健壮和响应迅速的应用程序。掌握这一模式是现代JavaScript异步编程的关键技能之一。

以上就是使用Async/Awjavascript java js node.js node 浏览器 回调函数 后端 ai 邮箱 应用开发 异步任务 JavaScript for try catch const 回调函数 循环 map 并发 JS 对象 promise 异步 重构 应用开发

大家都在看:

javascript java js node.js node 浏览器 回调函数 后端 ai 邮箱 应用开发 异步任务 JavaScript for try catch const 回调函数 循环 map 并发 JS 对象 promise 异步 重构 应用开发

ai
上一篇
下一篇
text=ZqhQzanResources