news 2026/5/1 5:46:01

《一文搞懂 JavaScript 事件循环(Event Loop):宏任务、微任务与 async/await》

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《一文搞懂 JavaScript 事件循环(Event Loop):宏任务、微任务与 async/await》

文章目录

  • JavaScript 事件循环(Event Loop):宏任务、微任务与 async/await
  • 1.预备知识
    • 1.1 同步和异步
  • 2.事件循环基础知识
    • 2.1 事件循环的组成-执行栈和任务队列
    • 2.2 事件循环(Event loop)的过程
    • 2.3 宏观任务 微观任务
  • 3.实战题目
    • 3.1 分析含有Promise的实战题目
    • 3.2 题目实战

JavaScript 事件循环(Event Loop):宏任务、微任务与 async/await

1.预备知识

1.1 同步和异步

在讨论事件循环前,首先需要先了解同步与异步的概念

JavaScript 是单线程语言,一行代码执行完才会再执行下一行,这个概念称之为同步。

但是如果取一个服务器中拿数据,需要等待 10 秒才能拿到,等待途中无法进行任何操作,是很糟糕的使用体验,于是就有了异步。

异步的程式或事件,并不会阻碍主线程执行其他代码,例如,拿取资料当作是一个异步事件,异步事件会在完成之后再通知主线程,而在这之中,主线程可以继续执行其他代码、使用者互动也不受异步事件的阻挡。而浏览器或其他的执行环境(例如Node.js) 之所以能够实践异步,正是因为有**事件循环(Event loop)**的机制。透过事件循环机制,能有效解决JavaScript 单执行绪的问题,让耗时的操作不会阻塞主线程。

2.事件循环基础知识

2.1 事件循环的组成-执行栈和任务队列

事件循环不存在JavaScript 本身,而是由JavaScript 的执行环境(浏览器或Node.js) 来实现的,其中包含几个概念:

  • 堆(Heap):堆是一种数据结构,拿来储存物件
  • 栈(Stack):采用后进先出的规则,当函数执行时,会被添加到栈的顶部,当执行完成时,就会从顶部移出,直到栈被清空
  • 队列(Queue):也是一种数据结构,特性是先进先出(FIFO)。在JavaScript 的执行环境中,等待处理的任务会被放在队列(Queue) 里面,等待栈(Stack) 被清空时,会从队列(Queue)中拿取第一个任务进行处理
  • 事件循环(Event loop):事件循环会不断地去查看栈(Stack) 是否空出,如果空出就会把队列(Queue)中等待的任务放进栈(Stack)中执行

2.2 事件循环(Event loop)的过程

1.User Interface(橙色部分)的User Interface,是浏览器页面,用户操作的来源,比如点击按钮,输入,页面展示等等

UI 不执行 JS,只是触发事件。

2.JavaScript Runtime(黄色部分),其中有 Heap(堆)和 Call Stack(调用栈)

Heap 负责,存数据,对象/数组/函数对象,不关心执行顺序,Heap=内存仓库。

Call Stack(调用栈),执行 JS 代码,函数一层一层压栈,单线程,只能有一个。

3.Web APIs(绿色部分)

理解事件循环的关键

这里的东西不是 JS 在跑,而是:浏览器提供的能力,独立于 JS 线程。

包括:

  • DOM 事件(click)
  • AJAX/fetch
  • 定时器(setTimeout)

JS 调用它们,只是登记一下

4.Callback Queue(蓝色部分)

回调的候车室,分成了宏任务队列(Task Queue) 微任务队列(Microtask Queue)

中间的循环箭头,是一种机制,不停的检查:call Stack 是否为空,空了就从 Queue 拿一个回调,不空就等着

完整执行流程的描述:

Step 1:

UI 触发 JS,同步代码直接执行

Step 2:

遇到 Web API(异步任务)(如 setTimeout)

1.setTimeout() 进 Call Stack

2.浏览器 Web API 接管计时

3.JS 立刻继续往下走(不等)

setTimeout(fn, 1000)

setTimeout() 本身是一次“同步函数调用”,所以一定会先进 Call Stack;但 setTimeout 里传的 callback,才会进入宏任务队列。

部分是什么去哪
setTimeout()同步函数调用Call Stack
fn(回调)异步回调宏任务队列(Task Queue)

Step 3:Web API 完成 → 回调进 Queue

  • 浏览器说:「callback 已经准备好了」
  • 放进 Callback Queue,不是立刻执行

Step 4:Event Loop 把回调塞回 Call Stack

条件只有一个:Call Stack 必须是空的

满足后:

  • callback → Call Stack
  • JS 开始执行回调函数

2.3 宏观任务 微观任务

JavaScript 中的异步任务又分成宏任务(Macro Task) 和微任务(Micro Task),这两者的执行顺序是不同的

常见的宏任务与微任务如下:

  • 宏任务:script(整体程式码)、setTimeoutsetInterval、I/O、事件、postMessageMessageChannelsetImmediate(Node.js)
  • 微任务:Promise.thenPromise.catchMutaionObserverprocess.nextTick(Node.js)。

async 函数本身:同步调用,await 的本质:Promise.then 的语法糖.

比如 async1 中有一个await async2()

await async2() 会同步立即执行 async2,并且把 await 后面的代码包装成一个「微任务」去执行。

执行顺序如下:

  • 执行一次宏任务(最开始会是整个srcipt所以上面的例子会先执行console.log(1))
  • 执行过程中如果遇到宏任务,就放进宏任务列队
  • 执行过程中如果遇到微任务,就放进微任务列队
  • 当执行栈空了,先检查微任务列队,如果有微任务,就依序执行直到微任务列队为空
  • 接着进行浏览器的渲染,渲然完后开始下一个宏任务(回到最开始的步骤)

3.实战题目

3.1 分析含有Promise的实战题目

Promise 和 setTimeout 都是同步注册异步任务的机制,只是 Promise 注册的是微任务,setTimeout 注册的是宏任务。

Promised 中的执行器函数是同步立即执行,.then .catch 才是异步的微任务

console.log('A');constp=newPromise((resolve,reject)=>{// 这段就是 executor(执行器函数)console.log('B: executor start');resolve('OK');console.log('C: executor end');});p.then((value)=>{console.log('E: then',value);});console.log('D');

resolve 会标记 Promise 为 fulfilled,并在「当前执行上下文结束后」派发微任务,将.then 加入到微任务队列中

「当前执行上下文结束后」指的是整个当前宏任务(script)执行完毕,也就是 call Stack 为空。

输出如下:

A B: executor start C: executor end' D

此时call Stack 栈空,将微任务队列加入 call Stack,保证call Stack 栈空后 1,微任务队列也为空,才能执行下一个宏任务

最终如下:

A B: executor start C: executor end' D E: then

promise 中的 resolve

.then 只有在 Promise「状态发生改变(resolve / reject)」的那一刻,才会被“派发”为微任务

Promise 的状态变化是同步的、确定的、不可回退的

resolve 在当前调用栈里已经执行了吗?

  • 执行了 → Promise 已 fulfilled → then 进微任务
  • 没执行 → Promise 还 pending → then 只注册

情况一: promise 立刻变成fulfilled

Promise.resolve().then(()=>{console.log("promise 1");});

这个 promise 立刻变成fulfilled

情况二:执行到 resolve,promise 状态变成fulfilled

setTimeout(function(){console.log("setTimeout 2");resolve("resolve 1");},0);}).then((res)=>{

这个就得等着resolve 执行才能变成fulfilled

情况三:不可预测

fetch('/api').then(res => { console.log('then'); });

Promise 的 resolve 时机是由外部事件决定的(如网络、I/O、定时器),

对静态代码分析来说是不可预测的,但对运行时来说是确定的

所以面试题一般都是明确告知 resolve

3.2 题目实战

题目一:

console.log(1);setTimeout(function(){console.log(2);},0);Promise.resolve().then(function(){console.log(3);}).then(function(){console.log(4);});

输出:

1; 3; 4; 2;

题目二:

console.log("begins");setTimeout(()=>{//1console.log("setTimeout 1");Promise.resolve().then(()=>{//2console.log("promise 1");});},0);newPromise(function(resolve,reject){// 3console.log("promise 2");setTimeout(function(){// 4console.log("setTimeout 2");resolve("resolve 1");},0);}).then((res)=>{//5console.log("dot then 1");setTimeout(()=>{//6console.log(res);},0);});

宏任务队列:script(整体程式码)

微任务队列:无

宏任务队列:(1) (4)

微任务队列:无

此时微任务队列为空应该继续执行宏任务(1)

宏任务队列:(4)

微任务队列:(2)

此时宏任务(1)执行完应该执行微任务(2)

此时应该继续执行加入新的宏任务

宏任务队列:(4)

微任务队列:

执行到这 resolve(“resolve 1”); (5)加入微任务队列

宏任务队列:

微任务队列:(5)

宏任务队列:(6)

微任务队列:

输出:

begins; promise 2; setTimeout 1; promise 1; setTimeout 2; dot then 1; resolve 1;

题目三:

asyncfunctionasync1(){console.log("async1 start");awaitasync2();console.log("async1 end");}asyncfunctionasync2(){console.log("async2");}console.log("script start");setTimeout(function(){console.log("setTimeout");},0);async1();newPromise(function(resolve){console.log("promise1");resolve();}).then(function(){console.log("promise2");});console.log("script end");

输出如下:

script start async1 start async2 promise1 script end async1 end promise2 setTimeout

注意:注意,await后的程式码会被放到微任务列队,所以不会马上印出'async1 end'而是会把它放到微任务列队

过程:

  • 执行代码
  • setTimeout 进入宏任务队列
  • 执行async1,然后呼叫await async2()所以印出’async2’。但是 async2 是同步立即执行的。注意,await`后的程式码会被放到微任务列队,所以不会马上印出’async1 end而是会把它放到微任务列队
  • 执行 promise 的执行器函数,promise 状态变成fullfiled,等待等待 call Stack 为空的时候将.then 加入微任务队列
  • 将两个微任务队列加入 call Stack,执行两个微任务有先后顺序
  • call Stack 为空,微任务队列为空,从宏任务队列中拿出一个宏任务加入 call Stack
  • 执行 call Stack
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 21:30:52

编写程序,模拟导购机器人推荐,输入顾客需求,如买手机,输出推荐商品及理由,如推荐某某手机,性价比比高。

我将为你设计一个模拟导购机器人推荐的程序,结合智能机器人导论的知识表示与推理机制,实现模块化、可扩展的商品推荐系统。核心代码实现# -*- coding: utf-8 -*-"""导购机器人推荐模拟程序 v1.0基于智能机器人导论的知识表示与推理机制输…

作者头像 李华
网站建设 2026/4/17 23:57:00

编写程序,模拟无人机航拍,输入拍摄高度和角度,输出无人机的飞行指令,确保拍摄到指定画面。

我将为你设计一个模拟无人机航拍的程序,结合智能机器人导论的坐标系转换与姿态控制原理,实现模块化、可扩展的飞行指令生成系统。核心代码实现# -*- coding: utf-8 -*-"""无人机航拍模拟程序 v1.0基于智能机器人导论的坐标系转换与姿态控…

作者头像 李华
网站建设 2026/4/25 23:16:56

大模型技术详解:从入门到精通,程序员必学知识,建议收藏!

本文详细介绍了大模型的基本概念、技术原理与应用场景。大模型是具有海量参数、需超强计算能力的AI系统,核心基于Transformer架构,通过预训练与微调学习知识。它能处理自然语言、图像等多模态任务,在内容生成、对话系统等领域有广泛应用。同时…

作者头像 李华
网站建设 2026/4/23 3:39:26

为什么顶尖团队都在用Ollama部署Open-AutoGLM?真相令人震惊

第一章:Shell脚本的基本语法和命令Shell脚本是Linux/Unix系统中自动化任务的核心工具,通过编写可执行的文本文件,用户能够组合命令、控制流程并处理数据。脚本通常以#!/bin/bash开头,称为Shebang,用于指定解释器路径。…

作者头像 李华
网站建设 2026/4/23 3:34:20

第 1 章 Linux 系统核心与 Shell 编程基础 ——SRE/DevOps 工程师的入门必修课

文章目录 第1章 Linux系统核心与Shell编程基础——SRE/DevOps工程师的入门必修课 1.1 Linux文件系统深度解析 1.1.1 虚拟文件系统(VFS)——Linux文件系统的抽象层 核心概念 实操演练:VFS核心对象的查看与验证 SRE实战场景:VFS相关故障排查 1.1.2 Linux核心目录功能详解 实操…

作者头像 李华
网站建设 2026/4/30 21:46:13

【机构级AI投研提示词曝光】:Open-AutoGLM中不为人知的10大分析模板

第一章:Open-AutoGLM股票分析提示词的核心架构Open-AutoGLM 是一个面向金融领域任务的提示工程框架,专为自动化股票分析场景设计。其核心架构围绕语义解析、上下文增强与多阶段推理展开,通过结构化提示词引导大语言模型完成从原始数据理解到投…

作者头像 李华