JavaScript 中的宏任务、微任务与同步任务:深入解析

一、引言

JavaScript 是一种单线程语言,这意味着它一次只能执行一个任务。为了处理异步操作(如网络请求、定时器等),JavaScript 使用了事件循环机制来管理任务的执行顺序。了解宏任务、微任务以及同步任务的区别对于掌握 JavaScript 的异步编程至关重要。

二、同步任务、宏任务与微任务

同步任务:

  • 同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕后,下一个任务才能开始。 常见的同步任务有赋值语句输出语句运算操作条件循环结构控制流语句。

宏任务(Macrotask):

  • 每个宏任务执行完之后,都会检查并执行所有的微任务。常见的宏任务包括 setTimeoutsetInterval、I/O、UI渲染等。

微任务(Microtask):

  • 微任务队列中的所有任务会在当前宏任务结束后立即执行。常见的微任务包括 Promise.then/catch/finallyMutationObserver 等。

三、Promise状态与方法

1、状态

每个 Promise 实例都有且仅有以下三种状态之一:

  1. Pending(待定)

    • 初始状态,表示 Promise 尚未被解决或拒绝。
    • 在这个状态下,Promise 的结果是未知的。
  2. Fulfilled(已成功)

    • 表示 Promise 已经被成功解决。
    • 通常通过调用 resolve 方法进入此状态。
  3. Rejected(已失败)

    • 表示 Promise 已经被拒绝。
    • 通常通过调用 reject 方法进入此状态。

Promise 的状态只能从 pending 转变为 fulfilledrejected,并且一旦状态发生变化,就不会再改变。这种不可逆的状态流转机制确保了 Promise 的可靠性。

js 复制代码
// 创建一个Promise
var promiseInstance = new Promise(() => {});
// resolve
var resolvedPromise = Promise.resolve();
// reject
var rejectedPromise = Promise.reject();
console.log(promiseInstance, resolvedPromise,rejectedPromise);

2、如何观察 Promise 的状态变化

由于 JavaScript 的设计限制,我们无法直接访问 Promise 的内部状态(如 pendingfulfilledrejected)。但我们可以通过以下方法间接观察状态变化:
  1. 通过 .then.catch

    • 使用 .then 可以捕获 fulfilled 状态的结果。
    • 使用 .catch 可以捕获 rejected 状态的错误。
js 复制代码
const promise = new Promise((resolve, reject) => { 
setTimeout(() => resolve("Success!"), 1000); }); 

promise .then((result) => console.log("Resolved:", result)) 
.catch((error) => console.log("Rejected:", error)); 
// 输出: Resolved: Success!
  1. 通过 Promise.resolve()Promise.reject()
  • Promise.resolve() 返回一个已经被解决的 Promise
  • Promise.reject() 返回一个已经被拒绝的 Promise
js 复制代码
const resolvedPromise = Promise.resolve("Resolved!"); 
const rejectedPromise = Promise.reject("Rejected!"); 
console.log(resolvedPromise); 
// 输出: Promise { <fulfilled>: 'Resolved!' } 
console.log(rejectedPromise); 
// 输出: Promise { <rejected>: 'Rejected!' }

3、Promise静态方法

方法/属性 特点 使用场景
Promise.all 等待所有 Promise 成功解决;若有一个失败,则立即失败。 需要确保所有任务都成功完成时使用。
Promise.race 返回第一个完成的 Promise 的结果(无论是成功还是失败)。 需要快速响应第一个完成的任务时使用。
Promise.allSettled 等待所有 Promise 完成,无论成功或失败;返回每个 Promise 的状态和结果。 需要知道所有任务的最终状态时使用。
Promise.any 返回第一个成功解决的 Promise;如果所有 Promise 都失败,则返回 AggregateError 只需要一个任务成功即可时使用。
finally 无论 Promise 是成功解决 (fulfilled) 还是失败 (rejected),finally 方法都会执行。 执行清理工作或后续步骤,不论 Promise 结果。

四、了解async和await

1. async 函数

  • 使用 async 关键字声明的函数总是返回一个 Promise
  • 如果函数返回一个普通值,该值会被自动包装成一个已解决的 Promise
  • 如果函数抛出异常,则返回的 Promise 会被拒绝。

2. await 关键字

  • await 只能在 async 函数内部使用。
  • 它会让当前 async 函数暂停执行,直到等待的 Promise 被解决或拒绝。
  • Promise 被解决后,await 表达式的值就是 Promise 的结果。

3. await 阻碍主线程?

JavaScript 是单线程语言,所有代码都在主线程上运行。当我们在代码中使用 await 时,它并不会真正"阻塞"整个程序的执行,而是通过事件循环机制来实现"暂停"的效果。

4. await 的底层机制

  • 当遇到 await 时,JavaScript 引擎会暂停当前 async 函数的执行,并将控制权交还给事件循环。
  • 等待的 Promise 被解决后,await 后续的代码会被放入微任务队列(Microtask Queue) 中。
  • 微任务队列中的代码会在当前宏任务(macrotask)完成后立即执行。

示例:观察 await 对代码执行的影响

js 复制代码
console.log("Start"); 
async function asyncFunction() 
{ 
console.log("Async Function Start"); 
await new Promise(resolve => setTimeout(resolve, 1000)); 
console.log("Async Function End"); 
} 
asyncFunction(); 
console.log("End");
//输出结果如下:
// Start 
// Async Function Start 
// End 
// (1秒后)Async Function End

例题总结

js 复制代码
async function asyncFunc1() {
  console.log('asyncFunc1 start');
  await asyncFunc2();
  console.log('asyncFunc1 end');
}

async function asyncFunc2() {
  console.log('asyncFunc2 start');
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('setTimeout in asyncFunc2');
      resolve();
    }, 0);
  });
}

console.log('script start');

setTimeout(() => {
  console.log('setTimeout outside');
}, 0);

asyncFunc1();

new Promise((resolve) => {
  console.log('promise creation');
  resolve();
}).then(() => {
  console.log('first then');
}).then(() => {
  console.log('second then');
});

console.log('script end');


//输出顺序
// script start 
// asyncFunc1 start 
// asyncFunc2 start 
// promise creation 
// script end 
// first then 
// second then 
// setTimeout outside 
// setTimeout in asyncFunc2 
// asyncFunc1 end

输出顺序的原因

  1. 同步代码优先执行

    • 所有同步代码(如 console.logPromise 构造器内部的代码)会立即执行。
  2. 微任务优先于宏任务

    • 当前宏任务完成后,微任务队列中的任务会优先执行。
    • Promise.thenawait 后续代码都会被注册为微任务。
  3. 宏任务按注册顺序执行

    • setTimeout 注册的宏任务会在微任务队列清空后依次执行。
  4. await 的暂停机制

    • await 会让当前 async 函数暂停执行,并将后续代码注册为微任务。
    • 只有当等待的 Promise 被解决后,这些微任务才会被执行。

通过理解事件循环机制和任务队列的执行顺序,我们可以清晰地解释JavaScript 中的宏任务、微任务与同步任务! 😊

相关推荐
田本初3 分钟前
vue-cli工具build测试与生产包对css处理的不同
前端·css·vue.js
inxunoffice1 小时前
批量在多个 PDF 的指定位置插入页,如插入封面、插入尾页
前端·pdf
木木黄木木1 小时前
HTML5 Canvas绘画板项目实战:打造一个功能丰富的在线画板
前端·html·html5
豆芽8191 小时前
基于Web的交互式智能成绩管理系统设计
前端·python·信息可视化·数据分析·交互·web·数据可视化
不是鱼1 小时前
XSS 和 CSRF 为什么值得你的关注?
前端·javascript
顺遂时光1 小时前
微信小程序——解构赋值与普通赋值
前端·javascript·vue.js
anyeyongzhou1 小时前
img标签请求浏览器资源携带请求头
前端·vue.js
Captaincc1 小时前
腾讯云 EdgeOne Pages「MCP Server」正式发布
前端·腾讯·mcp
最新资讯动态1 小时前
想让鸿蒙应用快的“飞起”,来HarmonyOS开发者官网“最佳实践-性能专区”
前端
雾岛LYC听风2 小时前
3. 轴指令(omron 机器自动化控制器)——>MC_GearInPos
前端·数据库·自动化