😲完了完了,forEach异步执行,怎么后面的先完成了!?

简介: 代码review,业务里的代码千奇百怪,到底还能遇到什么呢?oh no,真的有人在forEach里用异步调用!

嗨,大家好!这里是道长王jj~ 🎩🧙‍♂️

前端团队在对接各类接口的时候,80%的时间都在处理类似于下面的数组对象:

[{
   
    name: 'name01',
    age: '18',
    ...
}, {
   
    name: 'name02',
    age: '18',
    ...
}{
   
    name: 'name03',
    age: '18',
    ...
}, {
   
    name: 'name04',
    age: '18',
    ...
}]

而JavaScript的高级函数中就自带forEach这个遍历处理数组的方法。所以我们前端团队成员习惯性的会大量采用此方法对来源数据进行二次处理。

🏢 某天,我正在审查团队成员小明的代码时,发现了一个常见的问题:他在使用forEach方法处理异步操作时,并没有考虑到 forEach 不能保证异步任务执行顺序的问题。这给项目带来了潜在的风险和不可预测的结果。

❗review问题:forEach不能胜任异步任务!

小明在后台管理系统的开发中负责处理不同权限角色的功能。

为了快速实现不同角色的功能,他使用了forEach方法遍历角色列表并执行异步操作。然而,这种做法并不能保证异步任务按照预期的顺序执行。

下面是小明的代码片段:

async function processRoles() {
   
    let roles = ['admin', 'editor', 'employee'];
    roles.forEach(async role => {
   
            const result = await performTask(role);
            console.log(result);
    });
    console.log('任务处理完成');
}

function performTask(role) {
   
    return new Promise((resolve, reject) => {
   
        // 模拟异步操作
        setTimeout(() => {
   
                resolve(`已处理角色:${
     role}`);
        }, Math.random() * 1000);
    });
}

processRoles();

小明期望的输出结果是:

已处理角色:admin
已处理角色:editor
已处理角色:employee
任务处理完成

然而,实际的输出结果却是不确定的,可能会类似于以下情况:

任务处理完成
已处理角色:employee
已处理角色:admin
已处理角色:editor

😕 那有没有办法教教它乖乖排队呢?让我们看看该如何处理。

🧐问题溯源:forEach凭啥“随心所欲”

要理解这个问题的原因,我们需要了解forEach方法的底层实现方式。

forEach方法的核心逻辑如下:

for (var i = 0; i < length; i++) {
   
  if (i in array) {
   
    var element = array[i];
    callback(element, i, array);
  }
}

forEach方法直接遍历数组并执行回调函数,无法保证异步任务的执行顺序。如果后面的任务执行时间较短,就可能在前面的任务之前完成执行。

这就像在公司里,新同事按照名字的字母顺序逐个拜访每个同事,但每个同事的工作任务都不一样。如果某个同事恰好很快就完成了工作,而其他同事还在忙碌,那么新同事就会跟着顺序走,没有办法按照预期的顺序与同事们进行交流。

😔 这可让人头疼了!那有没有解决方案呢?

📌解决方案:for...of才能保证任务排队稳稳哒

为了解决这个问题,我们可以采用更可靠的方法,即使用for...of循环来确保异步任务按照预期顺序执行。

以下是改进后的代码示例:

async function processRoles() {
   
    let roles = ['admin', 'editor', 'employee'];
    for (const role of roles) {
   
        const result = await performTask(role);
        console.log(result);
    }
    console.log('任务处理完成');
}

通过使用for...of循环,我们可以确保异步任务按照预期顺序执行,避免了不确定性。

🚀 现在问题解决了!他重构了代码,使用了for...of循环来确保异步任务按照预期顺序执行。我们重新审查了他的代码,这次一切都按照预期运行,没有出现任何问题。💡

📖解决原理:迭代器出马,让任务听话

for...of循环实际上是基于迭代器(Iterator)的遍历方式。对于数组来说,它是一种可迭代对象,可以通过迭代器进行遍历。

我们可以通过以下方式获取数组的迭代器:

let roles = ['admin', 'editor', 'employee'];
let iterator = roles[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

迭代器返回的结果具有value和done属性,这使得我们可以使用迭代器来确保异步任务的顺序执行。

因此,我们的代码可以用迭代器进行如下组织:

async function processRoles() {
   
    let roles = ['admin', 'editor', 'employee'];
    let iterator = roles[Symbol.iterator]();
    let res = iterator.next();
    while(!res.done) {
   
        let value = res.value;
        console.log(value);
        console.log(await performTask(value));
        res = iterator.next();
    }
    console.log('任务处理完成');
}
processRoles()

输出结果如下:

admin
已处理角色:admin
editor
已处理角色:editor
employee
已处理角色:employee
任务处理完成

通过以上例子重新认识生成器(Generator)作为迭代器的特性,我们可以更深入地理解了for...of循环的原理和工作方式。

🎉 希望本文能够帮助你解决异步代码中按顺序执行的问题。如果你有任何疑问或者想进一步讨论相关话题,请随时告诉我。🚀✨

目录
相关文章
|
4月前
|
机器学习/深度学习 数据采集 人工智能
LangChain 视角的 Loop Engineering
Loop Engineering 是一个 AI Agent 构建方法论,它用调度、验证、触发、自改进四层循环替代人类持续驱动 Agent。LangChain 给出了这套循环的理论框架,OpenClaw 给出了它的工程实现。
667 0
|
3月前
|
人工智能 运维 数据中心
拆解光模块:从传统分立器件到硅光芯片的集成革命
本文揭秘光模块核心构成(TOSA/ROSA、激光器、调制器、探测器、DSP等),对比传统分立器件与硅光集成技术:前者成熟稳定,后者依托CMOS工艺将光器件“刻”入硅芯片,实现高集成、低功耗,但需外置光源。二者按距离、成本互补共存。(239字)
|
5月前
|
缓存 JSON 监控
淘宝拍立淘 API(taobao.item.search.img)
一句话说明:上传图片(URL/Base64),返回淘宝同款 / 相似商品列表,用于比价、内容带货、同款监控。
|
6月前
|
人工智能 监控 供应链
淘宝代运营爆款打造核心逻辑(附实操步骤)
本文基于淘宝官方规则与12年实战经验,提炼出一套科学、可复用的爆款打造方法论。涵盖产品选型(30%)、内容优化(25%)、流量运营(25%)、口碑维护(20%)四大维度,含8步实操流程与真实案例验证,助力商家提升转化、延长爆款生命周期。(239字)
1254 0
|
存储 分布式计算 算法
【交替方向乘子法】梯度下降法与交替方向乘子法(ADMM)求解LASSO问题研究(Matlab代码实现)
【交替方向乘子法】梯度下降法与交替方向乘子法(ADMM)求解LASSO问题研究(Matlab代码实现)
436 0
|
网络协议 安全
DHCP服务器配置
DHCP(动态主机配置协议)是一种用于自动分配IP地址及相关网络参数的协议,旨在简化网络管理、减少手动配置的工作量并避免IP地址冲突。其工作过程包括发现、提供、请求和确认四个阶段,确保设备快速安全接入网络。关键概念包括IP地址池、租约、作用域和静态分配等。DHCP通过自动化、动态性和冲突避免等功能,支持网络设备高效接入与管理。
797 21
|
缓存 监控 安全
告别缓存击穿!Go 语言中的防并发神器:singleflight 包深度解析
在高并发场景中,多个请求同时访问同一资源易导致缓存击穿、数据库压力过大。Go 语言提供的 `singleflight` 包可将相同 key 的请求合并,仅执行一次实际操作,其余请求共享结果,有效降低系统负载。本文详解其原理、实现及典型应用场景,并附示例代码,助你掌握高并发优化技巧。
910 0
|
监控 安全 测试技术
从0到1构建电商API:如何用规范设计省下百万维护成本?
本文系统解析电商API接口开发全流程,涵盖需求分析、架构设计、安全实践、测试上线及文档维护等关键环节,结合技术规范与实际案例,助力构建高可用、可扩展的电商系统。
|
Android开发
定制Android关机界面
定制Android关机界面
656 0