Promise链流程困惑:为何该异步函数仅返回单行结果?
原代码
async function chicks(nest, year) { let list = ""; await Promise.all(network(nest).map(async name => { list += `${name}: ${ await anyStorage(nest, name, `chicks in ${year}`) }\n`; })); return list; }
函数说明
该函数预期返回网络中所有巢穴及对应年份孵化雏鸟数量的列表,其中network(nest)返回所有巢穴名称数组,anyStorage以Promise形式返回指定巢穴的雏鸟数量。
用户困惑
- 为何list仅能记录单行内容,map遍历首个元素后list未更新?
- 为何仅响应最慢的巢穴信息被记录,而非数组最后一项?
- Promise.all如何处理内部包含Promise的嵌套Promise?
问题解答
为何list仅能记录单行内容,map遍历首个元素后list未更新?
这是因为map里的异步函数是并发执行的,多个操作同时修改同一个list变量会触发竞态条件:所有异步函数启动时拿到的list都是初始空字符串,执行list +=时,都是基于空字符串拼接自己的内容,最终所有操作相当于互相覆盖,只留下最后完成的那个结果,看起来就像只有单行。为何仅响应最慢的巢穴信息被记录,而非数组最后一项?
Promise.all会等所有传入的Promise完成,但各个anyStorage请求的完成顺序是不确定的——哪个请求最慢,它的list +=操作就最后执行。由于每个操作都是基于初始空字符串拼接,最后执行的那个就会覆盖掉之前所有操作的结果,所以留下的是最慢请求的内容,而非数组最后一项。Promise.all如何处理内部包含Promise的嵌套Promise?
Promise.all会自动识别并处理嵌套的Promise:当你传入的数组里是返回Promise的异步函数时,map会先把这些异步函数转换成Promise对象,Promise.all会等待这些Promise全部resolved(也就是等内部的await anyStorage全部完成),才会继续执行后续逻辑。简单说,不管数组里是直接的Promise还是异步函数返回的Promise,Promise.all都会等待它们全部完成。
修复方案
要避免竞态条件,应该先收集所有异步操作的结果,最后再拼接:
async function chicks(nest, year) { const results = await Promise.all(network(nest).map(async name => { const count = await anyStorage(nest, name, `chicks in ${year}`); return `${name}: ${count}\n`; })); return results.join(''); }
这样每个异步函数返回独立的拼接字符串,Promise.all收集完所有结果后用join合并,就能得到完整的列表了。
内容的提问来源于stack exchange,提问作者user10673486

