JavaScript事件循环怎样执行异步代码【教程】

JavaScript事件循环只调度已入队的回调,异步逻辑由宿主API执行;宏任务由宿主插入宏队列,微任务在当前任务后集中清空;Promise构造器同步执行,.then等为微任务;await等价于.then链;process.nextTick在Node.js中优先级高于微任务。

JavaScript事件循环不“执行”异步代码,它只按顺序调度和运行已进入任务队列的回调——真正执行异步逻辑的是浏览器或Node.js的底层API(如setTimeoutfetchPromise构造器内部的同步部分)。

宏任务和微任务的排队时机不同

宏任务(如setTimeoutsetInterval、I/O、UI渲染)由宿主环境插入宏任务队列;微任务(如Promise.thenMutationObserverqueueMicrotask)在当前任务结束后、下一个宏任务开始前集中清空。

  • Promise构造器里的函数是同步执行的,只有.then.catch注册的回调才是微任务
  • setTimeout(() => {}, 0)不会立刻执行,它至少要等完当前调用栈 + 所有微任务
  • 连续调用queueMicrotask会累积到同一轮微任务队列,不会触发多次事件循环迭代

Promise链的每个.then都生成新微任务

哪怕写成promise.then(a).then(b).then(c)abc也不是一次推入,而是a执行完后才把b推入微任务队列,b执行完再推c。这决定了错误传播和时序不可跳过中间环节。

  • 如果a抛错且没被.catch捕获,b会被跳过,c也不会执行
  • await本质上就是语法糖,等价于.then链,同样受微任务排队规则约束
  • 不要指望await Promise.resolve()return快——它多了一次微任务调度,实际更慢

Node.js与浏览器的process.nextTick优先级更高

在Node.js中,process.nextTick的回调在当前操作完成后立即执行,甚至早于微任务队列——它不属于标准事件循环规范,是Node.js特有机制,常被库用于紧急响应(如避免error事件丢失)。

  • 浏览器没有process.nextTick,用queueMicrotask最接近其语义
  • 过度使用process.nextTick可能导致I/O饥饿,因为它的回调会不断插队
  • Promise.resolve().then()queueMicrotask在浏览器中行为一致,但后者无兼容性风险(IE不支持Promise,但queueMicrotask也需Chrome 68+/Firefox 69+

事件循环本身没有“并发”或“并行”能力,所有回调都是单线程串行执行的;所谓“异步”,只是把一部分工作移交给了宿主环境,并约定好什么时候把回调塞进哪个队列——真正容易被忽略的,是那些看似同步却偷偷触发异步分支的操作,比如new Promise(resolve => { resolve(); })立刻产生微任务,而resolve()调用本身却是同步的。