0

0

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

霞舞

霞舞

发布时间:2025-12-02 14:59:01

|

470人浏览过

|

来源于php中文网

原创

深入理解 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 和回调函数的示例代码:

立即学习Java免费学习笔记(深入)”;

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 内部)

    Vondy
    Vondy

    下一代AI应用平台,汇集了一流的工具/应用程序

    下载
    • 输出 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 代码至关重要。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
es6新特性
es6新特性

es6新特性有:1、块级作用域变量;2、箭头函数;3、模板字符串;4、解构赋值;5、默认参数;6、 扩展运算符;7、 类和继承;8、Promise。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

106

2023.07.17

es6新特性有哪些
es6新特性有哪些

es6的新特性有:1、块级作用域;2、箭头函数;3、解构赋值;4、默认参数;5、扩展运算符;6、模板字符串;7、类和模块;8、迭代器和生成器;9、Promise对象;10、模块化导入和导出等等。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

197

2023.08.04

JavaScript ES6新特性
JavaScript ES6新特性

ES6是JavaScript的根本性升级,引入let/const实现块级作用域、箭头函数解决this绑定问题、解构赋值与模板字符串简化数据处理、对象简写与模块化提升代码可读性与组织性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

233

2025.12.24

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

447

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

606

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

766

2023.08.10

console接口是干嘛的
console接口是干嘛的

console接口是一种用于在计算机命令行或浏览器开发工具中输出信息的工具,提供了一种简单的方式来记录和查看应用程序的输出结果和调试信息。本专题为大家提供console接口相关的各种文章、以及下载和课程。

420

2023.08.08

console.log是什么
console.log是什么

console.log 是 javascript 函数,用于在浏览器控制台中输出信息,便于调试和故障排除。想了解更多console.log的相关内容,可以阅读本专题下面的文章。

541

2024.05.29

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号