JavaScript循环中Promise执行顺序异常问题排查
我创建了两个JavaScript函数:一个包含For循环,另一个是延迟1秒后打印文本的MyCustomPrintFunction。调用方式如下:
代码实现
Code block 1
console.log("Function Begins"); functionWithLoop().then(function (result) { console.log("Function Ends"); });
Code block 2
function functionWithLoop() { return new Promise(function(resolve, reject) { for (let loopcount = 1; loopcount <= 3; loopcount++) { MyCustomPrintFunction(loopcount).then(function(resolve) { console.log("After custom print function : index " + loopcount); }); } resolve(); }); }
Code block 3
function MyCustomPrintFunction(text) { return new Promise(function(resolve, reject) { setTimeout(() => { console.log("In custom print function " + text); resolve(); }, 1000); }); }
预期控制台日志顺序
Function Begins
In custom print function 1
After custom print function : index 1
In custom print function 2
After custom print function : index 2
In custom print function 3
After custom print function : index 3
Function Ends
实际控制台打印顺序
Function Begins
Function Ends
In custom print function 1
After custom print function : index 1
In custom print function 2
After custom print function : index 2
In custom print function 3
After custom print function : index 3
问题核心
为什么第一个then语句不等待整个循环执行完成?代码编写中哪里出错了?
解答
错误原因
你在functionWithLoop里的核心错误是:循环仅同步启动了所有异步操作(MyCustomPrintFunction返回的Promise),但完全没有等待这些异步操作完成,就直接调用了resolve()。
JavaScript的for循环是同步执行的,它会在极短时间内把3次MyCustomPrintFunction调用全部执行完毕,每个调用都会启动一个1秒后的定时器,但这些异步操作不会阻塞循环的执行流程。循环结束后立刻执行resolve(),导致functionWithLoop()返回的Promise直接进入已完成状态,外层的.then()自然会马上执行,打印Function Ends。
而那些定时器会在1秒后依次触发,所以Function Ends会出现在所有异步打印内容的前面。
修复方案
要让functionWithLoop等待所有异步操作完成后再resolve,有两种常用的正确写法:
方式1:用async/await实现顺序执行
把functionWithLoop改成async函数,在循环内部await每个MyCustomPrintFunction的执行,这样循环会等待当前异步操作完成后,再进入下一次迭代,完全符合你预期的顺序:
async function functionWithLoop() { for (let loopcount = 1; loopcount <= 3; loopcount++) { await MyCustomPrintFunction(loopcount); console.log("After custom print function : index " + loopcount); } }
方式2:用Promise.all()实现并行执行
如果希望3个异步操作同时启动(1秒后几乎同时输出),再等待所有操作完成后resolve,可以用Promise.all()收集所有Promise:
function functionWithLoop() { const promises = []; for (let loopcount = 1; loopcount <= 3; loopcount++) { const promise = MyCustomPrintFunction(loopcount).then(() => { console.log("After custom print function : index " + loopcount); }); promises.push(promise); } return Promise.all(promises); }
这种方式下,三个In custom print function会几乎同时打印,然后对应的After...依次输出,最后才打印Function Ends。如果需要严格按顺序执行异步操作,优先选择第一种async/await的写法。
内容的提问来源于stack exchange,提问作者Bluemarble

