0

0

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

霞舞

霞舞

发布时间:2025-09-28 13:03:00

|

499人浏览过

|

来源于php中文网

原创

使用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);
    }
});

代码解析

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

AdsGo AI
AdsGo AI

全自动 AI 广告专家,助您在数分钟内完成广告搭建、优化及扩量

下载
  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异步编程的关键技能之一。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

558

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

416

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

756

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

479

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

534

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1091

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

659

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

25

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号