谁说forEach不支持异步代码,只是你拿不到异步结果而已

在前面探讨 forEach 中异步请求后端接口时,很多人都知道 forEach 中 async/await 实际是无效的,很多文章也说:forEach 不支持异步,forEach 只能同步运行代码,forEach 会忽略 await 直接进行下一次循环…

当时我的理解也是这样的,后面一细想好像不对,直接上我前面一篇文章用到的示例代码:

async function getData() {const list = await $getListData()// 遍历请求list.forEach(async (item) => {const res = await $getExtraInfo({id: item.id})item.extraInfo = res.extraInfo})// 打印下最终处理过的额外数据console.log(list)
}

上面 $getListData、$getExtraInfo 都是 promise 异步方法,按照上面说的 forEach 会直接忽略掉 await,那么循环体内部拿到的 res 就应该是 undefined,后面的 res.extraInfo 应该报错才对,但是实际上代码并没有报错,说明 await 是有效的,内部的异步代码也是可以正常运行的,所以 forEach 肯定是支持异步代码的。

手写版 forEach

先从自己实现的简版 forEach 看起:

Array.prototype.customForEach = function (callback) {for (let i = 0; i < this.length; i++) {callback(this[i], i, this)}
}

里面会为数组的每个元素执行一下回调函数,实际拿几组数组测试和正宗的 forEach 方法效果也一样。可能很多人还是会有疑问你自己实现这到底靠不靠谱,不瞒你说我也有这样的疑问。

MDN 上关于 forEach 的说明

先去 MDN 上搜一下 forEach,里面的大部分内容只是使用层面的文档,不过里面有提到:“forEach() 期望的是一个同步函数,它不会等待 Promise 兑现。在使用 Promise(或异步函数)作为 forEach 回调时,请确保你意识到这一点可能带来的影响”。

ECMAScript 中 forEach 规范

继续去往 javascript 底层探究,我们都知道执行 js 代码是需要依靠 js 引擎,去将我们写的代码解释翻译成计算机能理解的机器码才能执行的,所有 js 引擎都需要参照 ECMAScript 规范来具体实现,所以这里我们先去看下 ECMAScript 上关于 forEach 的标准规范:
在这里插入图片描述

谷歌 V8 的 forEach 实现

常见的 js 引擎有:谷歌的 V8、火狐 FireFox 的 SpiderMonkey、苹果 Safari 的 JavaScriptCore、微软 Edge 的 ChakraCore…后台都很硬,这里我们就选其中最厉害的谷歌浏览器和 nodejs 依赖的 V8 引擎,V8 中对于 forEach 实现的主要源码:

transitioning macro FastArrayForEach(implicit context: Context)(o: JSReceiver, len: Number, callbackfn: Callable, thisArg: JSAny): JSAnylabels Bailout(Smi) {let k: Smi = 0;const smiLen = Cast<Smi>(len) otherwise goto Bailout(k);const fastO = Cast<FastJSArray>(o) otherwise goto Bailout(k);let fastOW = NewFastJSArrayWitness(fastO);// Build a fast loop over the smi array.for (; k < smiLen; k++) {fastOW.Recheck() otherwise goto Bailout(k);// Ensure that we haven't walked beyond a possibly updated length.if (k >= fastOW.Get().length) goto Bailout(k);const value: JSAny = fastOW.LoadElementNoHole(k)otherwise continue;Call(context, callbackfn, thisArg, value, k, fastOW.Get());}return Undefined;
}

源码是 .tq 文件,这是 V8 团队开发的一个叫 Torque 的语言,语法类似 TypeScript,所以对于前端程序员上面的代码大概也能看懂,想要了解详细的 Torque 语法,可以直接去 V8 的官网上查看。

从上面的源码可以看到 forEach 实际还是依赖的 for 循环,没有返回值所以最后 return 的一个 Undefined。看完源码是不是发现咱上面的手写版也大差不差,只不过 V8 里实现了更多细节的处理。

结论:forEach 支持异步代码

最后的结论就是:forEach 其实是支持异步的,循环时并不是会直接忽略掉 await,但是因为 forEach 没有返回值,所以我们在外部没有办法拿到每次回调执行过后的异步 promise,也就没有办法在后续的代码中去处理或者获取异步结果了,改造一下最初的示例代码:

async function getData() {const list = await $getListData()// 遍历请求list.forEach(async (item) => {const res = await $getExtraInfo({id: item.id})item.extraInfo = res.extraInfo})// 打印下最终处理过的额外数据console.log(list)setTimeout(() => {console.log(list)}, 1000 * 10)
}

你会发现 10 秒后定时器中是可以按照预期打印出我们想要的结果的,所以异步代码是生效了的,只不过在同步代码中我们没有办法获取到循环体内部的异步状态。

如果还是不能理解,我们对比下 map 方法,map 和 forEach 很类似,但是 map 是有返回值的,每次遍历结束之后我们是可以直接 return 一个值,后续我们就可以接收到这个返回值。这也是为什么很多文章中改写 forEach 异步操作时,使用 map 然后借助 Promise.all 来等待所有异步操作完成后,再进行下面的逻辑来实现同步的效果。

参考文档

  • MDN forEach 文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
  • ECMAScript 中 forEach 规范:https://tc39.es/ecma262/#sec-array.prototype.foreach
  • 谷歌 V8 中 forEach 源码:https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/array-foreach.tq#70
  • 谷歌 V8 中 map 源码:https://chromium.googlesource.com/v8/v8.git/+/refs/heads/9.0-lkgr/src/builtins/array-map.tq#192
  • 谷歌 V8 官网:https://v8.dev
  • 谷歌 V8 源码:https://github.com/v8/v8

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/869833.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dify/api/models/tool.py文件中的数据表

源码位置&#xff1a;dify/api/models/tool.py ToolProvider 表结构 字段英文名数据类型字段中文名字备注idStringUUIDIDUUID生成tenant_idStringUUID租户ID非空tool_nameString工具名称非空encrypted_credentialsText加密凭证可为空is_enabledBoolean是否启用默认值为 false…

[GICv3] 1.引言Introduction

基本概念 通用中断控制器 (GIC) 从外设获取中断&#xff0c;确定它们的优先级&#xff0c;然后将它们传送到适当的处理器内核。 下图了为一个 GIC 从 n 个不同的外设获取中断&#xff0c;并将它们分配给两个不同的处理器。 ​​ GCI(Generic Interrupt Controller)&#xff0c…

Caused by: java.lang.NoSuchMethodError: com.squareup.javapoet.MethodSpec

导入第三方module运行项目报&#xff1a; Caused by: java.lang.NoSuchMethodError: com.squareup.javapoet.MethodSpec$Builder.addComment(Ljava/lang/String;[Ljava/lang/Object;)Lcom/squareup/javapoet/MethodSpec$Builder; Caused by: java.lang.RuntimeException: Cann…

IPython的交互式命令行:交互式命令行界面

IPython的交互式命令行&#xff1a;交互式命令行界面 介绍 IPython是一款功能强大的交互式命令行工具&#xff0c;它极大地增强了Python编程的体验。通过提供即时反馈和动态探索功能&#xff0c;IPython帮助初学者更快速、更直观地掌握Python编程技能。本指南将详细介绍IPyth…

AI Agent 的发展现状、行业结构与趋势分析

Agent 来自一种哲学概念&#xff0c;是个很古老的哲学术语&#xff0c;从哲学意义上讲&#xff0c;“代理”的概念涉及实体的自主性&#xff0c;具有行使意志、做出选择和采取行动的能力&#xff0c;而不是被动地对外部刺激做出反应。后来人们将这一概念引入计算机科学领域&…

ApiFox或postman怎么用params类型传输json或集合+json的String类型

你是否碰见过这样的接口? post请求然后传输的参数都要和查询时一样以param形式传参数,那String什么的都好说,传就直接进后台了,那json呢,集合呢,是不是直接给你返400呢. 1.传json如何处理 那我们看看怎么实现,如果你要传json数据,那需要将特殊字符转义,也叫url转码,否则传不…

【HarmonyOS】关于官方推荐的组件级路由Navigation的心得体会

前言 最近因为之前的630版本有点忙&#xff0c;导致断更了几天&#xff0c;现在再补上。换换脑子。 目前内测系统的华为应用市场&#xff0c;各种顶级APP陆续都放出来beta版本了&#xff0c;大体上都完成了主流程的开发。欣欣向荣的气息。 学习思路 关于学习HarmonyOS的问题…

热点解读 | 小红书「县城生活」趋势前瞻

“县城婆罗门”、“月薪两万不如县城贵妇”、“北漂打工人回县城被穷笑”……继中产之后&#xff0c;县城成为又一个全网热议的焦点。 县城叙事重返舆论场&#xff0c;本期千瓜将进一步解构「县城」语境下的个体表现&#xff0c;帮助品牌沉淀用户心智&#xff0c;塑造新时代竞争…

​​​防御第一次作业

1、拓扑图及实验要求&#xff1a; 2、配置&#xff1a; 配置终端及服务器IP地址&#xff1a; Pc2&#xff1a; Client1&#xff1a; Pc4&#xff1a; Client2&#xff1a; PC1&#xff1a; Server1&#xff1a; Server2&#xff1a; 防火墙基础配置&#xff1a; [fw1]int g …

maven 依赖冲突

依赖冲突 1、对于 Maven 而言&#xff0c;同一个 groupId 同一个 artifactId 下&#xff0c;只能使用一个 version。 <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math3 --><dependency><groupId>org.apache.commons</groupId&…

力扣题解(等差数列划分)

413.等差数列划分 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0c;[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 给你一个整数数组 nums &#xff0c;返回数组 nums 中所有为等差数组…

KAN-Mixer每行代码解读

import torch import torch.nn.functional as F from torch import nn import math 这几行代码导入了PyTorch库,包括主要的torch模块、torch.nn.functional模块(通常用于激活函数等),torch.nn模块(用于定义神经网络组件),以及math模块(用于基本的数学运算)。device = …

手搓前端day1

断断续续的学了些前端&#xff0c;今天开始写写代码&#xff0c;就当是记录一下自己前端的成长过程 效果&#xff1a; 写了点css&#xff0c;实现了简单的前端页面的跳转 文件目录 代码如下&#xff1a; styles.css body{margin: 0;padding: 0;}header{background-color: bl…

企业级-释放锁进程

作者&#xff1a;fyupeng 技术专栏&#xff1a;☞ https://github.com/fyupeng 项目地址&#xff1a;☞ https://github.com/fyupeng/distributed-blog-system-api 留给读者 一、介绍 当由于某个业务id发生死循环一直在运行&#xff0c;那么再来一笔相同的业务id执行相同操作&…

系统服务综合作业

首先配置自动挂载 服务器的/rhce 自动挂载在客服端的/nfs/rhce 首先在服务器进行配置 dnf install nfs-utils -y [rootlocalhost ~]# vim /etc/exports [rootlocalhost ~]# firewall-cmd --permanent --add-servicenfs success [rootlocalhost ~]# firewall-cmd --permanen…

常见的嵌入式软件体系结构

本文介绍常见的嵌入式软件体系结构。 在嵌入式开发过程中&#xff0c;尤其是MCU为主控的项目开发中&#xff0c;实时性是需要考虑的重要因素&#xff0c;本文介绍常见的2种嵌入式软件体系结构&#xff0c;并对其优缺点作简要分析&#xff0c;另外&#xff0c;还对它们的软件层…

食物链之带权并查集解法

直接看题&#xff1a;https://www.acwing.com/problem/content/description/242/ 下面就是代码的实现了&#xff0c;因为自己与自己肯定是同类我们初始化为0. 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,k; int fk,x,y; int fa[10001…

Unity 资源管理方案(Resources、AssetDatabase、AssetBundle、Addressable)

文章目录 0.参考文章1.什么是资源管理1.1 资源管理文件1.2 资源管理目标1.3 资源管理内容 2.资源管理方案2.1 直接引用2.2 Resources2.3 AssetDatabase2.3.1 官方API2.3.2 AssetDatabase的工作原理 2.4 AssetBundle2.5 Addressable 0.参考文章 本文参考&#xff1a;https://zh…

【Python实战因果推断】29_倾向分9

目录 Outcome Is Easy to Model Generalized Propensity Score for Continuous Treatment Outcome Is Easy to Model 在下一个简单而又能说明问题的例子中&#xff0c;复杂性在于 而不是 。注意 中的非线性&#xff0c;而结果函数是简单的线性。在这里&#xff0c;真正的 A…

【操作系统】进程管理——调度算法(个人笔记)

学习日期&#xff1a;2024.7.4 内容摘要&#xff1a;各种调度算法的思想、规则、优缺点介绍 为什么要有调度算法&#xff1f; 调度算法就好比一群人在银行办理业务&#xff0c;准备办理业务的人就是进程/作业&#xff0c;银行窗口的工作人员就是CPU&#xff0c;进程往往是比C…