😲完了完了,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());

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

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

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循环的原理和工作方式。

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

目录
相关文章
|
存储 分布式计算 监控
深入浅出 HBase 实战 | 青训营笔记
Hbase是一种NoSQL数据库,这意味着它不像传统的RDBMS数据库那样支持SQL作为查询语言。Hbase是一种分布式存储的数据库,技术上来讲,它更像是分布式存储而不是分布式数据库,它缺少很多RDBMS系统的特性,比如列类型,辅助索引,触发器,和高级查询语言等待。
1483 0
深入浅出 HBase 实战 | 青训营笔记
|
JavaScript
VUE上传功能本地上传正常,打包上传后报错TypeError: ***.upload.addEventListener is not a function
VUE上传功能本地上传正常,打包上传后报错TypeError: ***.upload.addEventListener is not a function
1818 0
|
JavaScript
oninput 和 onchange 事件有什么区别
oninput 和 onchange 事件有什么区别
523 4
|
编解码 C# 数据库
C# + WPF 音频播放器 界面优雅,体验良好
【9月更文挑战第18天】这是一个用 C# 和 WPF 实现的音频播放器示例,界面简洁美观,功能丰富。设计包括播放/暂停按钮、进度条、音量控制滑块、歌曲列表和专辑封面显示。功能实现涵盖音频播放、进度条控制、音量调节及歌曲列表管理。通过响应式设计、动画效果、快捷键支持和错误处理,提升用户体验。可根据需求扩展更多功能。
521 3
|
Web App开发 JavaScript 前端开发
谁说forEach不支持异步代码,只是你拿不到异步结果而已
JavaScript 的 `forEach` 不直接支持异步操作,但可以在回调中使用 `async/await`。虽然 `forEach` 不会等待 `await`,异步代码仍会执行。MDN 文档指出 `forEach` 预期同步回调。ECMAScript 规范和 V8 源码显示 `forEach` 基于 for 循环实现,不返回 Promise。通过 `setTimeout` 可观察到异步操作完成。与 `map` 不同,`forEach` 不适合处理异步序列,常需转换为 `Promise.all` 结合 `map` 的方式。
265 11
|
前端开发 JavaScript API
Angular 与 RxJS 简直是天作之合!响应式编程最佳搭档,带你开启前端开发新境界!
【8月更文挑战第31天】在现代前端开发中,Angular 作为一款优秀框架,凭借其高性能和可扩展性脱颖而出。结合 RxJS,这一组合成为响应式编程的理想选择。RxJS 不仅优化了 Angular 的异步事件处理与数据流管理,还简化了复杂操作,如用户输入及网络请求,极大提升了代码的可读性和维护效率。通过示例应用展示,Angular 与 RxJS 的集成不仅使代码更清晰,还提供了更多处理异步任务的可能性,是提升开发质量的利器。
235 0
|
NoSQL 关系型数据库 MySQL
关于项目中 Repository 层的思考
关于项目中 Repository 层的思考
286 0
|
缓存 Java 网络安全
Nacos报错问题之获取配置文件的时候报错如何解决
Nacos是一个开源的、易于部署的动态服务发现、配置管理和服务管理平台,旨在帮助微服务架构下的应用进行快速配置更新和服务治理;在实际运用中,用户可能会遇到各种报错,本合集将常见的Nacos报错问题进行归纳和解答,以便使用者能够快速定位和解决这些问题。
2361 1
uniapp点击tabbar之前做判断
uniapp点击tabbar之前做判断
1002 1
|
消息中间件 安全 Java
Java中的异步编程方案总结
Java中的异步编程是一种能够提高程序性能和响应速度的技术。它通过将耗时的操作放在单独的线程中,让主线程继续执行其他任务,从而实现并发处理和异步执行。在Java中,异步编程常用的方式有多线程、Future和CompletableFuture等。在实际应用中,异步编程可以优化网络请求、数据库操作等IO密集型任务的性能,提高程序的响应速度和吞吐量。虽然异步编程可以带来许多好处,但同时也涉及到一些问题,比如线程安全、回调地狱等。因此,在使用异步编程时需要注意合理地设计和管理线程,确保程序的正确性和可维护性。
1049 1
Java中的异步编程方案总结

热门文章

最新文章