资讯动态

现代 JavaScript 教程(zh.javascript.info):深入理解 Promise 微任务队列、异步执行顺序与未处理的 rejection

发布时间:2026/10/8 19:16:26 来源:尧图企业网站定制
文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载本文基于 zh.javascript.info现代 JavaScript 教程中文版异步章节中 微任务Microtask 一文展开。你将理解为什么 promise 的.then/.catch/.finally处理程序总是异步执行掌握微任务队列microtask queue的调度规则学会控制异步代码的执行顺序并彻底弄清浏览器unhandledrejection事件触发的真实时机。读完本文你将具备排查异步顺序问题与 promise 错误处理问题的完整能力。一、现象立即 resolve 的 promise处理程序却迟到promise 的处理程序.then、.catch和.finally都是异步的。即使一个 promise 立即被 resolve.then、.catch和.finally下面的代码也会在这些处理程序之前被执行。示例代码如下let promise Promise.resolve(); promise.then(() alert(promise done!)); alert(code finished); // 这个 alert 先显示如果你运行它你会首先看到code finished然后才是promise done。这很奇怪因为这个 promise 肯定是一开始就完成的。为什么.then会在之后才被触发这是怎么回事要回答这个问题需要引入本教程异步体系中的核心概念——微任务队列。二、微任务队列Microtask queuePromiseJobs异步任务需要适当的管理。为此ECMA 标准规定了一个内部队列PromiseJobs通常被称为微任务队列microtask queueV8 术语。如规范中所述队列queue是先进先出的首先进入队列的任务会首先运行。只有在 JavaScript 引擎中没有其它任务在运行时才开始执行任务队列中的任务。或者简单地说当一个 promise 准备就绪时它的.then/catch/finally处理程序就会被放入队列中但是它们不会立即被执行。当 JavaScript 引擎执行完当前的代码它会从队列中获取任务并执行它。这就是为什么在上面那个示例中 code finished 会先显示。上图完整展示了这一流程promise.then(handler)调用发生后handler被入队handler enqueued而后续的同步代码如alert(code finished)立即执行只有当脚本执行完毕script execution finished后队列中的处理程序才会运行queued handler runs。关键规则处理程序永远经过队列promise 的处理程序总是会经过这个内部队列。如果有一个包含多个.then/catch/finally的链那么它们中的每一个都是异步执行的。也就是说它会首先进入队列然后在当前代码执行完成并且先前排队的处理程序都完成时才会被执行。三、如何控制执行顺序如果执行顺序对我们很重要该怎么办我们怎么才能让code finished在promise done之后出现呢很简单只需要像下面这样使用.then将其放入队列Promise.resolve() .then(() alert(promise done!)) .then(() alert(code finished));现在代码就是按照预期执行的。这里的关键思路是不要依赖紧随其后的同步代码而是把需要延后执行的代码本身也放进 promise 链中让它成为链上后续的微任务从而保证严格的先后顺序。四、未处理的 rejectionunhandledrejection的真正触发时机还记得 使用 promise 进行错误处理 一章中的unhandledrejection事件吗现在我们可以确切地看到 JavaScript 是如何发现未处理的 rejection 的。如果一个 promise 的 error 未被在微任务队列的末尾进行处理则会出现未处理的 rejection。正常来说如果我们预期可能会发生错误我们会在 promise 链上添加.catch来处理 errorlet promise Promise.reject(new Error(Promise Failed!)); promise.catch(err alert(caught)); // 不会运行error 已经被处理 window.addEventListener(unhandledrejection, event alert(event.reason));但是如果我们忘记添加.catch那么微任务队列清空后JavaScript 引擎会触发下面这事件let promise Promise.reject(new Error(Promise Failed!)); // Promise Failed! window.addEventListener(unhandledrejection, event alert(event.reason));如果我们迟一点再处理这个 error 会怎样例如let promise Promise.reject(new Error(Promise Failed!)); setTimeout(() promise.catch(err alert(caught)), 1000); // Error: Promise Failed! window.addEventListener(unhandledrejection, event alert(event.reason));现在如果我们运行上面这段代码我们会先看到Promise Failed!然后才是caught。如果我们并不了解微任务队列我们可能会想为什么unhandledrejection处理程序会运行我们已经捕获catch并处理了 error但是现在我们知道了当微任务队列中的任务都完成时才会生成unhandledrejection引擎会检查 promise如果 promise 中的任意一个出现 rejected 状态unhandledrejection事件就会被触发。在上面这个例子中被添加到setTimeout中的.catch也会被触发。只是会在unhandledrejection事件出现之后才会被触发所以它并没有改变什么没有发挥作用。这一节揭示了一个重要的工程结论在浏览器中unhandledrejection不是立即检测错误而是在当前微任务队列清空后统一检查。因此通过setTimeout这类宏任务去补救一个 rejection往往已经来不及阻止全局错误事件的上报真正的做法是在 promise 链内部尽早挂上.catch。五、微任务与宏任务事件循环中的位置在 事件循环微任务和宏任务 一章中本教程给出了更完整的事件循环算法与规范相比仍是简化过的从宏任务队列例如 script中出队并执行最早的任务。执行所有微任务当微任务队列非空时出队并执行最早的微任务。如果有变更则将变更渲染出来。如果宏任务队列为空则休眠直到出现宏任务。转到步骤 1。安排一个新的宏任务使用零延迟的setTimeout(f)。 安排一个新的微任务使用queueMicrotask(f)promise 处理程序也会通过微任务队列。每个宏任务之后引擎会立即执行微任务队列中的所有任务然后再执行其他的宏任务或渲染或进行其他任何操作。微任务会在执行任何其他事件处理、渲染或任何其他宏任务之前完成。这确保了微任务之间的应用程序环境基本相同没有鼠标坐标更改没有新的网络数据等。例如看看下面这个示例setTimeout(() alert(timeout)); Promise.resolve() .then(() alert(promise)); alert(code);这里的执行顺序是code首先显示因为它是常规的同步调用。promise第二个出现因为then会通过微任务队列并在当前代码之后执行。timeout最后显示因为它是一个宏任务。用一道练习验证微任务与宏任务的完整交织在 2-micro-macro-queue 练习 中教程给出了一个综合性的输出顺序问题其 解答 一步步演示了队列的进进出出console.log(1); setTimeout(() console.log(2)); Promise.resolve().then(() console.log(3)); Promise.resolve().then(() setTimeout(() console.log(4))); Promise.resolve().then(() console.log(5)); setTimeout(() console.log(6)); console.log(7);推导过程立即输出数字1和7因为简单的console.log调用没有使用任何队列。主代码流程执行完成后开始执行微任务队列其中有console.log(3); setTimeout(...4); console.log(5)输出3和5同时setTimeout(() console.log(4))把console.log(4)追加到了宏任务队列尾部。当微任务队列为空后开始执行宏任务队列输出2、6和4。最终输出结果为1 7 3 5 2 6 4。这个练习清晰地印证了本文的核心规则微任务队列总是先于宏任务队列被清空且微任务之间不会插入渲染或新的事件处理。补充async/await的幕后在 async/await 一章中本教程指出await是 promise 处理的另一种形式——它同样依赖微任务队列在幕后工作。因此理解微任务队列也是理解async/await暂停与恢复机制的基础await会暂停函数执行但不会阻塞引擎因为其恢复逻辑正是经由微任务队列安排的。总结Promise 处理始终是异步的因为所有 promise 行为都会通过内部的 promise jobs 队列也被称为微任务队列V8 术语。.then/catch/finally处理程序总是在当前代码完成后才会被调用且链上的每个处理程序都独立进入队列、按先进先出顺序逐个执行。如果我们需要确保一段代码在.then/catch/finally之后被执行我们可以将它添加到链式调用的.then中。unhandledrejection事件的触发时机是微任务队列清空之后引擎此时统一检查仍处于 rejected 状态的 promise。延迟如通过setTimeout添加.catch往往赶不上无法阻止全局错误事件。在大多数 JavaScript 引擎中包括浏览器和 Node.js微任务microtask的概念与事件循环event loop和宏任务macrotasks紧密相关。详细的调度算法与queueMicrotask用法见 事件循环微任务和宏任务 一章promise 错误的捕获与再抛出策略见 使用 promise 进行错误处理 一章。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐深入理解 JavaScript 微任务队列Microtask QueuePromise 异步执行与 unhandledrejection 的底层机制深入理解 JavaScript 微任务队列Microtask QueuePromise 异步执行与 unhandledrejection 的底层机制 本篇文档/教程前端Yi-VL-6B-hf革命性多模态AI模型深度解析视觉问答新体验Yi VL 6B hf革命性多模态AI模型深度解析视觉问答新体验 在人工智能飞速发展的今天 Yi VL 6B hf 作为一款革命性的多模态AI模型正在重DeepSeek-V4-Pro-Base终极指南百万上下文MoE架构深度解析DeepSeek V4 Pro Base终极指南百万上下文MoE架构深度解析 DeepSeek V4 Pro Base作为当前最先进的混合专家模型以其104上一篇使用 Docker 本地部署 Prisma Server从初始化、部署服务到升级维护的完整指南下一篇Apache Arrow生产环境编译优化高性能跨语言数据处理框架部署指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑