如何在顶层Promise中等待所有then()回调执行完成后再resolve该Promise?
我明白你的困扰了——你以为用Promise.all等完所有文件Promise之后,后续的then回调会同步执行,但实际上哪怕Promise已经fulfilled,then里的回调还是会被放到微任务队列,得等当前同步代码跑完才会执行,所以你的resolve(settings)会先触发,之后那些JSON解析的逻辑才会执行。
咱们先拆解下问题出在代码的哪个环节:
你在Promise.all(Object.values(settings.files)).then(() => { ... })里面,又循环遍历每个文件Promise并调用then,这些then都是独立的异步操作,当前的then块并不会等待它们完成,直接就走到resolve(settings)了,自然会出现日志顺序颠倒的情况。
那怎么改呢?其实咱们可以在收集文件Promise的时候,就把JSON解析的逻辑整合进去,这样Promise.all就能等待所有文件「获取+解析」的完整流程都完成,之后再resolve最终的settings对象。
修改后的代码大概是这样的:
function buildSettings(settings_file_names){ return new Promise(function(resolve, reject){ let settings = {}; settings.files = {}; settings.getFile = function(file_name){ return this.files[file_name]; }; // 收集所有包含「获取+解析」逻辑的Promise const filePromises = settings_file_names.map((file_name) => { return getLocalFile(file_name) .then((filedata) => { if (filedata && file_name.includes(".json")) { console.log("Parsing into JSON!"); return JSON.parse(filedata); } return filedata || null; }) .then((processedData) => { // 把处理好的数据直接赋值到settings.files settings.files[file_name] = processedData; }) .catch(reject); // 别忘了捕获单个文件的错误,避免Promise被吞掉 }); // 等待所有文件的获取+解析都完成 Promise.all(filePromises) .then(() => { console.log("About to resolve!"); resolve(settings); }) .catch(reject); }); }
为什么这样就能解决问题?
- 我们用
map遍历文件名,对每个文件,先调用getLocalFile,接着在同一个Promise链里完成JSON解析(如果是json文件的话),然后把处理好的数据赋值到settings.files。 - 把所有这些完整的Promise链收集到
filePromises数组里,再用Promise.all等待它们全部完成。这时候所有文件的获取、解析、赋值操作都已经做完了,再resolve(settings)就没问题了。
另外还有个小细节:你原来的代码里写了settings.file[filename],这里应该是settings.files[filename](少了个s),我在修改后的代码里也帮你修正了这个笔误。
还有个优化点:其实可以不用手动包裹一层新的Promise,直接返回Promise.all的结果链,这样代码更简洁,也符合Promise的最佳实践:
function buildSettings(settings_file_names){ const settings = { files: {}, getFile(file_name) { return this.files[file_name]; } }; const filePromises = settings_file_names.map((file_name) => { return getLocalFile(file_name) .then((filedata) => { if (filedata && file_name.includes(".json")) { console.log("Parsing into JSON!"); return JSON.parse(filedata); } return filedata || null; }) .then((processedData) => { settings.files[file_name] = processedData; }); }); return Promise.all(filePromises).then(() => { console.log("About to resolve!"); return settings; }); }
总结一下核心问题:你之前是先等所有文件Promise完成,然后再触发一批异步的解析操作,但没等这些解析完成就resolve了。正确的做法是把每个文件的「获取+解析」整合成一个完整的Promise,然后等待所有这些Promise完成,再返回最终的settings对象。
备注:内容来源于stack exchange,提问作者Shen

