为何含await的groupNotesPromise.map未执行完,console.log(groupNotes)就已打印?
我编写了一个从MongoDB获取数据的async函数getGroup,代码如下:
const getGroup = async (req, res) => { const { id } = req.params try { const group = await Group.findOne({ _id: id }); const groupNotesPromise = await group.group_notes.map( async (fileId) => { const file = await File.findById(fileId) return file; }) const groupNotes = await groupNotesPromise.map( async (notePromise) => { await notePromise.then(res => console.log(res)) }) console.log(groupNotes) } catch (error) { console.log(err.message); res.status(400).json({ err: err.message }); } }
但我发现,尽管groupNotesPromise.map内部使用了await,console.log(groupNotes)却先于该map的执行完成前就打印了。控制台输出结果如下:
[ Promise { <pending> }, Promise { <pending> } ] { _id: new ObjectId("6496c645bf2bb491b931f082"), fileName: 'test', fileAwsReference: 'b4b71d06828d23d857b3611c2ce2480cf704f1fc1b9cb1879b563159631d162a', createdBy: new ObjectId("648bab3c8044efccc461ad1d"), createdIn: new ObjectId("648d7776284835591f536552"), __v: 0 }, { _id: new ObjectId("6496c9fafb91e60683fc812d"), fileName: 'waeewaea', fileAwsReference: '0e1f17f127f9a12a9e27d765bda1436297f0d58eb7d20f3e9dc8d39e64b58c5b', createdBy: new ObjectId("648bab3c8044efccc461ad1d"), createdIn: new ObjectId("648d7776284835591f536552"), __v: 0 }
请问这一现象的原因是什么?
核心原因:map结合async返回Promise数组,await无法直接等待数组完成
第一个错误点:
await作用于map返回的数组无效group.group_notes.map(async (fileId) => {...})会生成一个Promise数组,但await只能等待单个Promise对象,不能直接处理数组。所以groupNotesPromise本质就是这个未被处理的Promise数组,此处的await完全不起作用。第二个错误点:二次
map生成新Promise数组,未等待全部完成
你通过groupNotesPromise.map(async (notePromise) => {...})又生成了新的Promise数组,同样await这个数组不会等待所有Promise执行完毕,groupNotes直接就是处于pending状态的Promise数组,因此console.log(groupNotes)会优先打印出pending的Promise,而数组内部的异步操作(await notePromise.then(...))会在后续执行,所以文件数据会晚于groupNotes的打印结果出现。额外问题:catch块变量名不匹配
catch的参数是error,但代码里写的是err.message,这会导致err未定义的报错。
修正后的代码
正确做法是使用Promise.all()等待所有Promise完成,简化后的代码如下:
const getGroup = async (req, res) => { const { id } = req.params try { const group = await Group.findOne({ _id: id }); // 生成Promise数组,无需在map前加await const groupNotesPromises = group.group_notes.map(fileId => File.findById(fileId)); // 用Promise.all等待所有Promise完成,获取实际文件数组 const groupNotes = await Promise.all(groupNotesPromises); console.log(groupNotes); // 可将结果返回给前端 res.status(200).json({ group, groupNotes }); } catch (error) { console.log(error.message); res.status(400).json({ err: error.message }); } }
关键说明
Promise.all()接收Promise数组作为参数,会等待所有Promise都resolved后,返回一个包含所有结果的数组,结果顺序与原Promise数组完全一致。- 无需额外包装async函数,因为
File.findById本身就返回Promise,直接传入map即可。
内容的提问来源于stack exchange,提问作者tan shi yu

