深入理解 JavaScript Promise 异步执行顺序与微任务队列


本文深入探讨 javascript 中 promise 异步函数的执行机制,特别是 `then` 方法如何与微任务队列(promisejob queue)协同工作。通过一个具体代码示例,我们将逐步解析代码执行流程、promise 状态变化以及回调函数入队与出队的时机,揭示 `console.log` 输出顺序背后的原理,帮助开发者掌握 promise 异步行为的精确控制。

JavaScript 异步编程基础与 Promise 机制

JavaScript 是一种单线程语言,这意味着它一次只能执行一个任务。为了处理耗时的操作(如网络请求、文件读写),JavaScript 引入了异步编程机制。Promise 是 ES6 引入的一种处理异步操作的模式,它代表一个异步操作的最终完成(或失败)及其结果值。

理解 Promise 的执行顺序,关键在于把握 JavaScript 的事件循环(Event Loop)、调用栈(Call Stack)、微任务队列(Microtask Queue,也称 PromiseJob Queue)和宏任务队列(Macrotask Queue)的概念。

  • 调用栈 (Call Stack):同步代码执行的地方。当函数被调用时,它被推入栈中;函数返回时,它被弹出。
  • 微任务队列 (Microtask Queue):存放 Promise 的 then/catch/finally 回调函数等。微任务在当前宏任务执行完毕后,且在下一个宏任务开始前,会被清空。
  • 宏任务队列 (Macrotask Queue):存放如 setTimeout、setInterval、I/O 操作等回调。在清空微任务队列后,事件循环会从宏任务队列中取出一个任务执行。

Promise 的核心特性:

  1. Promise.resolve():创建一个已解决(fulfilled)的 Promise,这个操作本身是同步的。
  2. then() 方法
    • 当一个 Promise 被解决(fulfilled)时,其 .then() 中注册的回调函数会被放入微任务队列。
    • then() 方法本身总是返回一个新的 Promise,这个新 Promise 的状态最初是 pending。它的解决或拒绝取决于 then 回调函数的执行结果。即使 then() 是在一个已解决的 Promise 上调用的,它返回的 Promise 仍然是 pending,因为其回调需要异步执行。

示例代码分析

为了更清晰地演示 Promise 的执行顺序,我们使用一个带有命名 Promise 和回调函数的示例代码:

var a = Promise.resolve(); // Promise a 立即解决

var b = a.then(function a_then() {
  console.log(1);
  var c = Promise.resolve(); // Promise c 立即解决
  var d = c.then(function c_then() {
    console.log(2);
  });
  var e = d.then(function d_then() {
    console.log(3);
  });
  console.log(4);
});

var f = b.then(function b_then() {
  console.log(5);
  var g = Promise.resolve(); // Promise g 立即解决
  var h = g.then(function g_then() {
    console.log(6);
  });
  var i = h.then(function h_then() {
    console.log(7);
  });
  console.log(8);
});

console.log(9);

我们将逐行分析这段代码的执行流程,并观察 console.log 的输出顺序。

详细执行流程解析

以下是代码执行的详细步骤,关注调用栈、Promise 状态和微任务队列的变化:

阶段一:同步代码执行

  1. var a = Promise.resolve();

    • a 被创建并立即进入 fulfilled 状态。
    • a 的状态: fulfilled。
    • 微任务队列: 空。
  2. var b = a.then(function a_then() { ... });

    • a.then() 被调用。由于 a 已经 fulfilled,a_then 回调函数被添加到微任务队列。
    • b 被创建,状态为 pending。
    • a 的状态: fulfilled;b 的状态: pending。
    • 微任务队列: [a_then]。
  3. var f = b.then(function b_then() { ... });

    • b.then() 被调用。由于 b 仍是 pending 状态,b_then 回调函数在 b 解决后才会被添加。
    • f 被创建,状态为 pending。
    • a 的状态: fulfilled;b 的状态: pending;f 的状态: pending。
    • 微任务队列: [a_then]。
  4. console.log(9);

    • 同步代码执行,输出 9。
    • 输出: 9
    • 微任务队列: [a_then]。

至此,所有同步代码执行完毕。调用栈清空,事件循环开始检查微任务队列。

阶段二:第一次微任务队列处理 (a_then)

  1. 事件循环从微任务队列中取出 a_then 并执行。

    • 微任务队列: 空。
  2. console.log(1); (在 a_then 内部)

    • 输出 1。
    • 输出: 9, 1
  3. var c = Promise.resolve();

    • c 被创建并立即进入 fulfilled 状态。
    • c 的状态: fulfilled。
  4. var d = c.then(function c_then() { ... });

    • c.then() 被调用。由于 c 已经 fulfilled,c_then 回调函数被添加到微任务队列。
    • d 被创建,状态为 pending。
    • 微任务队列: [c_then]。
  5. var e = d.then(function d_then() { ... });

    • d.then() 被调用。由于 d 仍是 pending 状态,d_then 回调函数在 d 解决后才会被添加。
    • e 被创建,状态为 pending。
    • 微任务队列: [c_then]。
  6. console.log(4); (在 a_then 内部)

    • 输出 4。
    • 输出: 9, 1, 4
  7. a_then 函数执行完毕,其返回值(undefined)用于解决 b。

    • b 的状态变为 fulfilled。
    • 由于 b 已经 fulfilled,之前注册在 b.then() 上的 b_then 回调函数现在被添加到微任务队列。
    • 微任务队列: [c_then, b_then]。

阶段三:第二次微任务队列处理 (c_then)

  1. 事件循环从微任务队列中取出 c_then 并执行。

    • 微任务队列: [b_then]。
  2. console.log(2); (在 c_then 内部)

    • 输出 2。
    • 输出: 9, 1, 4, 2
  3. c_then 函数执行完毕,其返回值(undefined)用于解决 d。

    • d 的状态变为 fulfilled。
    • 由于 d 已经 fulfilled,之前注册在 d.then() 上的 d_then 回调函数现在被添加到微任务队列。
    • 微任务队列: [b_then, d_then]。

阶段四:第三次微任务队列处理 (b_then)

  1. 事件循环从微任务队列中取出 b_then 并执行。

    • 微任务队列: [d_then]。
  2. console.log(5); (在 b_then 内部)

    • 输出 5。
    • 输出: 9, 1, 4, 2, 5
  3. var g = Promise.resolve();

    • g 被创建并立即进入 fulfilled 状态。
    • g 的状态: fulfilled。
  4. var h = g.then(function g_then() { ... });

    • g.then() 被调用。由于 g 已经 fulfilled,g_then 回调函数被添加到微任务队列。
    • h 被创建,状态为 pending。
    • 微任务队列: [d_then, g_then]。
  5. var i = h.then(function h_then() { ... });

    • h.then() 被调用。由于 h 仍是 pending 状态,h_then 回调函数在 h 解决后才会被添加。
    • i 被创建,状态为 pending。
    • 微任务队列: [d_then, g_then]。
  6. console.log(8); (在 b_then 内部)

    • 输出 8。
    • 输出: 9, 1, 4, 2, 5, 8
  7. b_then 函数执行完毕,其返回值(undefined)用于解决 f。

    • f 的状态变为 fulfilled。
    • 微任务队列: [d_then, g_then]。

阶段五:第四次微任务队列处理 (d_then)

  1. 事件循环从微任务队列中取出 d_then 并执行。

    • 微任务队列: [g_then]。
  2. console.log(3); (在 d_then 内部)

    • 输出 3。
    • 输出: 9, 1, 4, 2, 5, 8, 3
  3. d_then 函数执行完毕,其返回值(undefined)用于解决 e。

    • e 的状态变为 fulfilled。
    • 微任务队列: [g_then]。

阶段六:第五次微任务队列处理 (g_then)

  1. 事件循环从微任务队列中取出 g_then 并执行。

    • 微任务队列: 空。
  2. console.log(6); (在 g_then 内部)

    • 输出 6。
    • 输出: 9, 1, 4, 2, 5, 8, 3, 6
  3. g_then 函数执行完毕,其返回值(undefined)用于解决 h。

    • h 的状态变为 fulfilled。
    • 由于 h 已经 fulfilled,之前注册在 h.then() 上的 h_then 回调函数现在被添加到微任务队列。
    • 微任务队列: [h_then]。

阶段七:第六次微任务队列处理 (h_then)

  1. 事件循环从微任务队列中取出 h_then 并执行。

    • 微任务队列: 空。
  2. console.log(7); (在 h_then 内部)

    • 输出 7。
    • 输出: 9, 1, 4, 2, 5, 8, 3, 6, 7
  3. h_then 函数执行完毕,其返回值(undefined)用于解决 i。

    • i 的状态变为 fulfilled。
    • 微任务队列: 空。

最终输出顺序为:9, 1, 4, 2, 5, 8, 3, 6, 7。

注意事项与总结

  1. 同步优先原则:JavaScript 总是优先执行所有同步代码。只有当调用栈清空后,事件循环才会检查微任务队列。
  2. 微任务队列的优先级:微任务队列在每个宏任务(包括主脚本的执行)结束后立即清空。这意味着在一个宏任务内部产生的微任务,会在当前宏任务结束,但下一个宏任务开始之前被执行。
  3. then() 返回的新 Promise 总是 pending:理解 then() 方法返回的 Promise 总是从 pending 状态开始非常重要。它的最终状态取决于其回调函数的执行结果,而回调函数是异步执行的。
  4. Promise 链式调用:当一个 Promise 解决后,其 .then() 回调会被加入微任务队列。如果这个回调内部又返回了一个 Promise,那么下一个 .then() 的回调会等待这个内部 Promise 解决后才会被加入微任务队列。
  5. Promise.resolve() 的同步性:Promise.resolve() 本身是同步的,它会立即创建一个已解决的 Promise。但是,对这个 Promise 调用 .then() 所注册的回调,仍然是异步的,会被放入微任务队列。

通过以上详细的步骤分析,我们可以清晰地看到 JavaScript Promise 异步机制的内部工作原理,特别是微任务队列在调度 Promise 回调中的核心作用。掌握这些概念对于编写健壮、可预测的异步 JavaScript 代码至关重要。


# javascript  # es6  # java  # 回调函数  # mac  #  


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 网络优化76771 】 【 技术知识130152 】 【 IDC云计算60162 】 【 营销推广131313 】 【 AI优化88182 】 【 百度推广37138 】 【 网站推荐60173 】 【 精选阅读31334


相关推荐: Win11声音太小怎么办_Windows 11开启响度均衡增强音量【技巧】  c++如何实现多态性_c++ 虚函数表原理与动态绑定机制【教程】  如何在 IIS 上为 ASP.NET 6 应用排除特定目录并交由 PHP 处理  Win10如何更改开机密码_Windows10登录选项更改密码  如何在Golang中实现微服务负载均衡_Golang负载均衡策略与实现示例  mac怎么安装字体_MAC添加第三方字体与字体册管理【教程】  Win10怎么关闭自动更新错误弹窗_Win10策略屏蔽失败提示减少干扰【防护】  如何使用Golang匿名函数_快速定义临时函数逻辑  php内存溢出怎么排查_php内存限制调试与优化方法【说明】  Python代码测试策略_质量保障解析【教程】  如何使用Golang recover捕获panic_防止程序崩溃并处理异常  php订单日志怎么记录评价_php记录订单评价日志方法【方法】  如何在 Go 中比较自定义的数组类型(如 [20]byte)  c++ std::atomic如何保证原子性 c++ CAS操作原理【底层】  Python与OpenAI接口集成实战_生成式AI应用场景解析  Win11如何连接Xbox手柄 Win11蓝牙连接游戏手柄教程【步骤】  如何在Golang中实现自定义Benchmark_Golang testing.B自定义性能测量示例  Windows蓝屏错误0x0000001E怎么修复_KMODEEXCEPTIONNOTHANDLED排查  如何在Golang中处理通道发送接收错误_防止阻塞或panic  MAC怎么在照片中添加水印_MAC自带编辑工具文字水印叠加【方法】  php中作用域操作符能访问私有静态属性吗_访问权限限制【指南】  如何在 Go 中可靠地测试含 time.Time 字段的结构体  Windows 11怎么设置默认解压软件_Windows 11为ZIP/RAR文件指定默认打开程序  Python集合操作技巧_高效去重解析【教程】  如何使用Golang反射将map转换为struct_Golang reflect类型映射技巧  Python函数参数高级用法_默认值与可变参数解析【教程】  Mac如何彻底清理浏览器缓存?(Safari与Chrome)  Win10怎样设置多显示器_Win10多显示器扩展设置【攻略】  c++中如何使用auto关键字_c++11类型推导用法说明  Python文件和流处理指南_高效读写大体积数据文件  Win11任务栏怎么放到顶部_Win11修改任务栏位置方法【详细】  VSC怎样在Linux运行PHP_Ubuntu系统配置步骤【操作】  如何使用Golang安装API文档生成工具_快速生成接口文档  Windows蓝屏错误0x0000002C怎么解决_系统IO异常排查方法  Flask 表单数据通过 SMTP 发送邮件的完整实现教程  windows 10专注助手怎么关闭_windows 10禁用通知提醒功能方法  C++中的Pimpl idiom是什么,有什么好处?(隐藏实现)  Windows 11如何查看系统激活密钥_Windows 11使用CMD或PowerShell命令找回Product Key  Win10任务栏天气和资讯怎么关闭 Win10禁用新闻和兴趣功能【教程】  Windows10如何更改计算机工作组_Win10系统属性修改Workgroup  C#如何使用Channel C#通道实现异步通信  如何提升Golang程序I/O性能_Golang I/O密集型程序优化示例  Python变量绑定机制_引用模型解析【教程】  如何在 ACF 中正确更新嵌套多层的 Group 字段子字段  如何在 Go 中调用动态链接库(.so)中的函数  如何使用Golang写入二进制文件_Golang io Write二进制写入示例  c++怎么调用nana库开发GUI_c++ 现代风格窗口组件与事件处理【实战】  Win11怎样安装搜狗输入法_Win11安装搜狗输入法教程【步骤】  Win11怎么关闭定位服务_保护Win11位置隐私设置指南【详解】  如何从 Go 的 map[string]interface{} 中安全获取值 

 2025-12-02

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

致胜网络推广营销网


致胜网络推广营销网

致胜网络推广营销网专注海外推广十年,是谷歌推广.Facebook广告全球合作伙伴,我们精英化的技术团队为企业提供谷歌海外推广+外贸网站建设+网站维护运营+Google SEO优化+社交营销为您提供一站式海外营销服务。

 915688610

 17370845950

 915688610@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.