async关键字如何影响函数调用栈?each循环异步执行差异解析
问题解析与async机制说明
首先明确核心差异的原因:Cheerio的.each()是同步遍历方法,而async函数的执行逻辑会拆分同步和异步阶段,这直接导致了两种场景下的报错时机不同。
两种场景的执行顺序拆解
1. 不带async的同步回调
当你传入普通同步函数时,.each()会依次同步执行每个回调:
- 第一次循环:执行
console.log(variableInfo)打印初始值,紧接着执行variableInfo = ...的重赋值操作,立即触发TypeError(const变量不可重赋值),遍历直接终止,因此只会看到一次打印。
2. 带async的异步回调
async函数本质是Promise的语法糖,执行时会拆分为两个阶段:
- 同步阶段:调用async函数后,会先立即执行函数内的同步代码(比如你的
console.log(variableInfo)),直到遇到第一个await(哪怕函数里没有await,async函数也会把后续逻辑包装进微任务)。 - 异步微任务阶段:同步代码执行完后,函数会暂停并返回一个pending状态的Promise,此时Cheerio的
.each()是同步的,不会等待这个Promise完成,直接继续调用下一个循环的async回调,于是所有循环的同步打印都会先完成。 - 当所有同步代码执行完毕后,事件循环进入微任务队列处理阶段,开始执行每个async函数中暂停的后续代码,第一个执行到
variableInfo = ...的操作时,抛出TypeError,此时后续的微任务会被终止,所以你会看到先打印多次初始值,再出现报错。
async函数的实际工作机制
- 返回Promise:任何async函数调用后都会返回一个Promise对象,哪怕函数里没有
return语句,最终也会返回一个resolved状态的Promise,值为undefined。 - 同步优先执行:调用async函数时,会先同步执行函数内的所有代码,直到遇到
await关键字或者函数结束:- 如果遇到
await,会将await后面的表达式转换为Promise,然后暂停函数执行,让出执行权,让调用栈中的其他同步代码先执行。 - 如果没有
await,函数的全部逻辑会被包装成一个resolved的Promise,但同步代码仍然会立即执行,只是后续的错误(比如你的const重赋值)会在微任务阶段抛出。
- 如果遇到
- 错误处理:async函数内的未捕获错误会被自动捕获并作为Promise的reject原因,如果调用者没有通过
.catch()或try/catch处理这个reject,错误会在微任务阶段以“未捕获的Promise rejection”形式抛出,这也是为什么你会在所有同步打印之后才看到报错的原因。
内容的提问来源于stack exchange,提问作者merkkupilot
相关产品推荐
相关产品推荐

