新闻中心

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

2025-12-02
浏览次数:
返回列表

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

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

J*aScript 异步编程基础与 Promise 机制

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

理解 Promise 的执行顺序,关键在于把握 J*aScript 的事件循环(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。

    SCISPACE SCISPACE

    AI论文研究助手,探索和解释论文的平台

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

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

以上就是深入理解 J*aScript Promise 异步执行顺序与微任务队列的详细内容,更多请关注其它相关文章!


# 加载  # 内蒙古鹤壁网站推广  # 清水河网站推广公司  # 达州网站优化关键词  # 线上整合营销推广推荐  # 嘉兴优化网站怎么收费  # 芜湖网站建设seo优化品牌企业  # 全网霸屏营销推广怎么做  # 网站产品推广怎么做的好  # 曲周饲料网站建设方案  # 建瓯seo网站优化培训  # 仍然是  # 链式  # javascript  # 弹出  # 仍是  # 表单  # 清空  # 返回值  # 才会  # 回调  #   # mac  # 回调函数  # java  # es6 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: cad如何更改注释性对象的比例_cad注释性比例调整方法  Golang如何实现状态模式管理对象状态_Golang State模式实现技巧  支付宝碰一碰设备是REDMI手机吗 博主拆机辟谣:处理器、内存都不一样  一加 Nord 5 隐私权限异常_一加 Nord 5 系统安全优化  期待已久:小米17 Ultra、小米首款NAS本月登场  mysql如何设置表访问权限_mysql表访问权限配置  React Router 嵌套组件中 URL 重定向问题的解决方案  漫蛙2在线漫画入口 漫蛙正版漫画网页版直达  Win10如何恢复误删的快捷方式_Win10重建常用软件快捷方式  漫蛙2(台版)官方入口地址 漫蛙2(台版)正版漫画网页端  1688商家版怎样分析买家画像精准供货_1688商家版分析买家画像精准供货【供货策略】  QQ邮箱网页版入口页面 QQ邮箱在线登录入口官网  微信怎么把收藏的内容分类管理 微信收藏内容标签分类方法  抓大鹅解压小游戏 抓大鹅摸鱼解压入口  解决 Express.js 中 PUT 请求密码修改失败的路由配置指南  React/Next.js中实现列表项的动态移动与状态管理:兼论唯一键的重要性  修复二维数组索引越界异常:一维循环到二维坐标的正确映射  使用Python高效删除Word宏并转换DOCM为DOCX格式  树莓派传感器触发:通过Twilio API发送WhatsApp消息教程  PDF怎么合并PDF并保持格式_PDF合并文件保持排版教程  c++中的std::basic_string的SSO优化_c++短字符串优化深度解析  拼多多视频播放卡顿如何处理 拼多多视频播放优化技巧  12306选座系统怎么选连座_12306选座多人连坐操作方法  PostgreSQL海量数据高效导入策略:Python与Django实践指南  如何高效处理PHP中的Excel数据导入导出?PortPHP/Spreadsheet助你轻松搞定!  黑鲨3Pro怎样在相册开漫画风滤镜_iPhone黑鲨3Pro相册开漫画风滤镜【趣味滤镜】  win11 Snap Layouts怎么用 Win11窗口布局与分屏多任务高效指南【必学】  深入理解字体排版:Adobe光学字偶距与CSS字偶距的差异与实现  FullCalendar 自定义按钮样式定制指南  腾讯QQ邮箱登录入口_QQ邮箱官方网站使用地址  火锅吃太多会怎样 火锅吃太多会上火吗  在J*a中如何使用Stream.map转换元素_Stream映射操作解析  zookeeper 都有哪些功能?  jQuery Mask 插件中实现电话号码固定前导零的教程  12306怎么选座位选到安静区_12306选座安静区域选择策略  怎样更改Windows系统的默认安装路径_避免C盘爆满的终极设置【技巧】  Excel Power Pivot如何处理XML数据源 构建高级数据模型  使用Pandas转换并合并DataFrame:多列映射至统一结构  qq邮箱发邮件给国外发不出去_QQ邮箱国际邮件发送失败原因与解决  Go语言JSON解析深度指南:动态访问与结构体映射实践  J*aScript中高效清空DOM列表元素:解决for循环中断与任务管理问题  steam官方网页快速访问 steam账号注册全流程  想当下一个《2077》?《心之眼》Steam评价升至"多半好评"  必由学官方登录入口 必由学教师学生账号快速访问  Yandex搜索引擎官方地址 俄罗斯网络世界的主要入口  Win11文件资源管理器卡顿怎么修 Win11重置资源管理器进程优化响应速度【修复方法】  三星ZFold5多任务卡顿_Samsung ZFold5流畅度提升  Golang如何安装Swagger工具_GoSwagger文档生成环境  C++指针和引用有什么区别_C++内存管理核心概念深度解析  Win10系统服务哪些可以禁用 Win10安全优化服务列表【干货】 

搜索