NodeJS中Array.map()调用async函数引发数据错乱问题咨询
问题原因
你遇到的问题和Array.map中使用async函数无关,问题完全出在async函数内部的实现上,核心错误有三个:
- 不能在forEach的回调中使用await:Array.forEach是同步遍历方法,就算你给它传入async回调,它也不会等待回调里的异步操作执行完成,会直接遍历完所有key后结束。这就导致你的helper函数会在加密、S3上传操作还没完成的时候就提前返回对象,同时你在非async的forEach回调里写await本身就是语法错误,Node12环境会触发未捕获异常,进一步打乱执行顺序。
- 直接修改引用对象导致互相覆盖:JS中对象是引用传递,你传入helper的o就是原users数组里的对象,没有做拷贝就直接修改字段。多个并行执行的helper异步操作会修改同一块内存中的值,后面执行的def用户的加密结果赋值会覆盖掉abc用户的银行信息,这就是你看到字段错乱的核心原因。
- 字段赋值逻辑错误:你代码里else分支写的
o[k] = k.toUpperCase()会把字段的值直接替换为字段名的大写,比如name字段的值会从原名变成NAME,这是明显的逻辑疏漏。
修复方案
- 把所有forEach遍历替换为for...of遍历,只有这种遍历方式才能正确等待await的异步操作执行完成
- 处理每个用户对象前先做深拷贝,避免修改原对象导致的互相干扰
- 修正字段赋值的逻辑错误
- 修正原代码中
Object.Keys大小写错误、箭头函数缺箭头的语法问题
修复后的参考代码如下:
// Node12可使用v8模块自带的structuredClone做深拷贝,也可替换为其他深拷贝实现 const { structuredClone } = require('v8'); module.exports.topLevel = async () => { const users = [ { id: 'abc', name: 'George P. Burdell', job: 'Jack of all trades', married: true, bankInformation: { institution: 'Bank of America', routingNumber: 987654321, accountNumber: 665471235774 } }, { id: 'def', name: 'Jay-Z', job: 'Artist', married: true, bankInformation: { institution: 'Chase', routingNumber: 123456789, accountNumber: 97822651348 } } ] const transformedUsers = await Promise.all(users.map(helper)) return transformedUsers } async function helper(rawUser) { // 先深拷贝对象,不修改原数据 const o = structuredClone(rawUser) // 用for...of替代forEach for (const k of Object.keys(o)) { if (k === 'bankInformation') { o[k] = await encryptBank(o[k]) await s3.putObject({Body: JSON.stringify(o[k]),Bucket: bankBucketName, Key: `${o.id.toLowerCase()}.json`}).promise() } else if (k === 'married') { o[k] = !o[k] } else { // 修正赋值逻辑,按需调整为值转大写/键转大写,此处为值转大写示例 o[k] = o[k].toUpperCase() } } return o } async function encryptBank(bankInfo) { const o = structuredClone(bankInfo) for (const k of Object.keys(o)) { o[k] = await encrypt(o[k]) } return o }
相关说明
在Array.map中使用async函数是非常常规的合理写法,只要外层用Promise.all包裹等待所有异步任务完成即可,不需要回避这种用法。你遇到的问题完全是内部遍历实现错误导致的,和map本身无关。
内容的提问来源于stack exchange,提问作者Alex Ketchum
相关产品推荐
相关产品推荐

