Node.js中for循环不等待异步函数执行完成的问题排查
问题原因与解决方案
你的核心问题很明确:GetPage函数没有返回Promise对象,而await关键字只能等待Promise的状态变更(完成/失败)。目前你的GetPage只是一个普通函数,内部用的是回调式的异步操作(request和fs.writeFile),但没有把这些异步操作包装成Promise,所以await根本没法感知到异步任务什么时候完成,自然会直接进入下一次循环。
解决方案1:把GetPage包装成Promise
我们可以手动把回调风格的异步操作封装到Promise里,让GetPage返回这个Promise,这样await就能正确等待每个任务完成:
const request = require('request'); const fs = require('fs'); function GetPage(url){ // 返回一个Promise对象 return new Promise((resolve, reject) => { console.log(` Downloading page ${url}`); request({ url: url },(err,res,body) => { if(err) { reject(err); // 异步操作失败时,把错误传给Promise的reject return; } console.log(` Writing html to file` ); fs.writeFile(`${url.split('/').slice(-1)[0]}`, body, (err) => { if(err) { reject(err); return; } console.log('saved'); resolve(); // 异步操作全部完成时,调用resolve通知Promise结束 }); }); }); } var list = [ 'https://www.someurl.com/page1.html', 'https://www.someurl.com/page2.html', 'https://www.someurl.com/page3.html' ] const main = async () => { for(let i = 0; i < list.length; i++){ console.log(` processing ${list[i]}`); await GetPage(list[i]); // 现在await能正确等待Promise完成 } }; main().catch(console.error);
修改后运行,输出就会变成你期望的顺序:
processing https://www.someurl.com/page1.html Downloading page https://www.someurl.com/page1.html Writing html to file saved processing https://www.someurl.com/page2.html Downloading page https://www.someurl.com/page2.html Writing html to file saved processing https://www.someurl.com/page3.html Downloading page https://www.someurl.com/page3.html Writing html to file saved
解决方案2:用现代异步API简化代码
request库已经被标记为废弃了,推荐使用支持Promise的HTTP库(比如axios、node-fetch),同时Node.js的fs模块也提供了Promise风格的API(fs.promises),可以让代码更简洁,避免嵌套回调:
const axios = require('axios'); const fs = require('fs').promises; // 直接把GetPage写成async函数,内部用await处理异步操作 async function GetPage(url){ console.log(` Downloading page ${url}`); try { // 用axios发起请求,本身返回Promise const response = await axios.get(url); console.log(` Writing html to file` ); const filename = url.split('/').slice(-1)[0]; // 用fs.promises.writeFile,返回Promise await fs.writeFile(filename, response.data); console.log('saved'); } catch (err) { // 捕获错误并抛出,让main函数的catch处理 throw err; } } var list = [ 'https://www.someurl.com/page1.html', 'https://www.someurl.com/page2.html', 'https://www.someurl.com/page3.html' ] const main = async () => { // 用for...of循环更简洁 for(const url of list){ console.log(` processing ${url}`); await GetPage(url); } }; main().catch(console.error);
这种写法更符合现代JavaScript的异步编程风格,代码可读性更强,也更容易维护。
关键知识点回顾
await只能作用于Promise对象,如果函数没有返回Promise,await会直接跳过等待,继续执行后续代码。- 回调风格的异步函数(比如老式的
request、fs.writeFile)需要转成Promise才能和async/await配合使用,你可以手动封装,也可以用Node.js内置的util.promisify工具转换。
内容的提问来源于stack exchange,提问作者osu desu
相关产品推荐
相关产品推荐

