宏任务 (macro-task) 与微任务 (micro-task)
在 JavaScript 中,宏任务(macro-task)和微任务(micro-task)是任务队列(task queue)中两个不同的任务类型,它们是 JavaScript 异步编程机制的重要组成部分。
事件循环(Event Loop)是 JavaScript 中处理异步操作的机制,它确保代码按预期顺序执行,同时保持 js 单线程的特性。事件循环不断检查任务队列,按照一定规则处理同步代码、宏任务和微任务,并决定何时执行它们。
宏任务是事件循环中的主要任务队列;宏任务会在每轮事件循环中依次执行,执行完一个宏任务后,事件循环会检查是否有需要执行的微任务队列。常见的宏任务:主代码块(script)、setTimeout、setInterval、I/O 操作、UI rendering。
微任务优先级更高,它会在当前宏任务执行完后立即执行,并在下一个宏任务开始前完成。微任务可以在不等到下一轮事件循环的情况下进行。常见的微任务:Promise.then / Promise.catch / Promise.finally、MutationObserver、queueMicrotask。
注意:当 new Promise 时,此时视为宏任务的状态。当
resolve
或reject
时,把它们加入到微任务队列中,例如练习 2。
总结:事件循环每次迭代会执行一个宏任务的所有脚本,然后处理所有微任务。
执行顺序:
① JavaScript 先执行当前宏任务中的同步代码;
② 如果执行过程中遇到微任务,它会被加入微任务队列,但不会立即执行;
③ 当前宏任务中的所有同步代码执行完毕后,会执行微任务队列中的所有微任务,直到队列为空;
④ 微任务队列清空后,事件循环将继续执行下一个宏任务。
console.log('宏任务开始'); // 同步代码,立即执行
setTimeout(() => {
console.log('宏任务:setTimeout'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('微任务:Promise 1'); // 微任务
}).then(() => {
console.log('微任务:Promise 2'); // 微任务
});
console.log('宏任务结束'); // 同步代码,立即执行
按照上面的内容,分析执行步骤:
-
宏任务:执行整体代码(相当于
<script>
中的代码):
输出:宏任务开始
遇到setTimeout
,加入宏任务队列,当前宏任务队列 [setTimeout
]
遇到Promise.resolve().then()
,加入微任务,当前微任务队列 [promise1
]
输出:宏任务结束 -
微任务:执行微任务队列 [
promise1
]
输出:微任务:promise1
遇到then
,加入微任务队列,当前微任务队列 [then()
]
【微任务队列中不为空】执行微任务队列 [then()
],输出:微任务:promise2 -
执行渲染操作,更新界面(敲黑板划重点)。
-
宏任务:执行
setTimeout
输出:宏任务:setTimeout
练习
在线运行 JavaScript ,在这个网站上输出下方练习的结果。
要就来45道Promise面试题一次爽到底 这里有更多的关于 Promise 的面试题。
练习 1
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
})
console.log('1', promise1);
练习 2
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve('success')
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
练习 3
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
练习 4
const fn = () =>
new Promise((resolve, reject) => {
console.log(1);
resolve("success");
});
console.log("start");
fn().then(res => {
console.log(res);
});
练习 5
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
练习 6:
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
练习 7
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(() => {
console.log('promise')
})
}, 0)
setTimeout(() => {
console.log('timer2')
}, 0)
console.log('start')
练习 8
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
运行结果
练习 1:
promise1
1 Promise {<pending>}
练习 2
1
2
4
3
练习 3:
这里 promise.then()
不会执行
1
2
4
练习 4
这里 fn
函数包裹着 new Promise
,因此只有当 fn
被调用时,里面的 new Promise
才会被执行。
start
1
success
练习 5
script start
script end
promise1
promise2
setTimeout
练习 6:
① 从上到下同步完后,微任务队列并没有任务,而宏任务队列有一个 setTimeout
,因此下一个执行宏任务队列里的任务;
② 当完成执行完 setTimeout
,微任务队列里存在 Promise.then()
;
③ 执行 Promise.then()
的内容。
1
2
4
timerStart
timerEnd
success
练习 7
① 同步整个代码后,宏任务队列里面含有第一个 setTimeout
和第二个 setTimeout
,而微任务队列里为空;
② 取出第一个 setTimeout
,然后执行代码。执行完成后,微任务队列新增了一个 Promise.then
;
③ 执行 Promise.then
;
④ 执行第二个 setTimeout
。
start
timer1
promise
timer2
练习 8
考察 Promise
的三种状态。
'promise1' Promise{<pending>}
'promise2' Promise{<pending>}
test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102
'promise1' Promise{<resolved>: "success"}
'promise2' Promise{<rejected>: Error: error!!!}