JavaScript两种async/await写法的性能及其他优劣势对比
两种async/await遍历写法的差异说明
性能层面的差异
两种写法不存在脱离场景的绝对性能优势,性能表现完全匹配其执行逻辑,不存在某一种写法通用性能更好的结论:
- 如果你需要串行处理每个Promise的返回结果:
for await...of是原生语法实现,没有额外封装开销,是这个场景下性能最优的选择;如果强行用forEach实现串行处理,需要额外写递归、Promise链式调用等封装逻辑,反而会带来不必要的性能损耗。 - 如果你需要并行处理所有Promise的返回结果:
forEach传入async回调的写法天然支持并行注册回调,没有额外包装开销;如果强行用for await...of实现并行效果,需要提前把所有Promise包装成数组再配合Promise.all使用,多了一层语法包装的开销,性能反而不如forEach直接。
前提澄清:题目明确所有x均为已实例化的Promise对象,JS中Promise实例一旦创建就会立即启动内部异步逻辑,两种写法都不会改变Promise本身的启动执行时机,所有差异都集中在「遍历处理Promise结果」的逻辑层。
其他核心层面的区别
两种写法的代码示例如下:
// 第一种写法 for await (const a of [x1, x2, x3, x4]) { // do stuff }
// 第二种写法 [x1, x2, x3, x4].forEach(async (a) => { // do stuff })
二者除性能外的核心差异包括:
- 执行顺序逻辑完全不同
for await...of是严格串行的处理逻辑:会按照数组顺序,等待当前项的Promise状态落定、执行完当前循环体的全部逻辑之后,才会开始处理下一个数组项。举个例子:如果x1是3秒后resolve、x2是1秒后resolve,循环会先等3秒拿到x1的结果、处理完对应逻辑,才会去读取x2的结果(此时x2早就执行完成,会直接返回值),整个处理流程的总耗时 = 耗时最长的Promise执行时间 + 所有循环体逻辑的累计执行时间。forEach传入async回调的写法是并行处理逻辑:遍历数组时会立刻为所有Promise注册回调,不会等待上一个回调执行完成就处理下一项,所有循环体逻辑会在对应Promise落定后立刻触发,没有等待顺序。还是上面x1 3秒、x2 1秒的例子,x2落定后会立刻执行对应的循环体逻辑,不需要等x1处理完成,整个处理流程的总耗时 = 耗时最长的Promise执行时间,和数组项数量无关。 - 错误处理机制完全不同
for await...of的错误捕获符合常规代码直觉:循环过程中任何一个Promise reject、或是循环体内抛出异常,都可以直接被外层包裹的try/catch捕获,捕获错误后会自动中断后续循环执行。forEach写法的错误无法被外层try/catch捕获:因为forEach本身不会等待内部async函数返回的Promise,相当于所有回调抛出的reject都没有被接收,很容易触发全局的UnhandledPromiseRejection报错,除非你在每一个async回调内部单独写try/catch,否则错误会直接泄漏到全局。 - 循环控制能力不同
for await...of是原生迭代语法,支持所有常规循环控制能力:你可以在循环体内用break中断整个遍历、用continue跳过当前项、用return提前退出外层函数,也可以配合yield实现生成器逻辑。forEach是数组原型方法,传入的回调里无法使用break/continue这类循环控制关键字,也没有原生方法提前终止遍历,除非主动抛出异常强行中断,否则一定会遍历完数组所有项。 - 异步流程感知能力不同
for await...of本身在async上下文执行,外层async函数会自动等待整个循环的所有逻辑执行完成,你可以直接在循环后面写全部任务处理完成的后续逻辑。forEach本身返回undefined,不会感知内部async回调的执行状态,你无法直接拿到所有异步任务执行完成的时机,如果要等待所有逻辑跑完,需要手动维护一个Promise数组收集每个回调的返回值,再手动调用Promise.all等待,否则外层后续逻辑会在所有异步任务还没执行完的时候就提前运行。
内容的提问来源于stack exchange,提问作者Barney Chambers
相关产品推荐
相关产品推荐

