宏任务与微任务

宏任务与微任务

之前写过关于事件循环机制的文章 js 的并发模型一文,当时以为已经讲清楚所有这方面的概念了,但是最近又发现,事件循环机制还有宏任务与微任务这个概念没有涉及,所以这里延续之前的文章,再继续讲一讲。

概念

在之前的博客里已经讲清楚了事件循环机制是如何运行的(js 的并发模型 地址)。

当时是这么理解的,当执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中按顺序获取任务来执行(task-> task-> task…)

这里补充一个概念,浏览器为了能够使得 JS 内部 task(任务) 与 DOM 任务能够有序的执行,会在一个 task 执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task-> 渲染-> task->…)

宏任务(task):就是上述的 JS 内部(任务队列里)的任务,严格按照时间顺序压栈和执行。如 setTimeOut、setInverter等

微任务(Microtask ):通常来说就是需要在当前 task 执行结束后立即执行的任务,例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。microtask 任务队列是一个与 task 任务队列相互独立的队列,microtask 任务将会在每一个 task 任务执行结束之后执行。每一个 task 中产生的 microtask 都将会添加到 microtask 队列中,microtask 中产生的 microtask 将会添加至当前队列的尾部,并且 microtask 会按序的处理完队列中的所有任务。microtask 类型的任务目前包括了 MutationObserver 以及 Promise 的回调函数。

或许看完还是一脸懵逼,所以还是得分析例子

例子

例子1:

console.log("script start");
Promise.resolve().then(function(){
	console.log("promise1")
})
setTimeout(function(){
    console.log("setTimeout")
},0);
Promise.resolve().then(function(){
	console.log("promise2")
})
console.log("script end");

//输出
//script start
//script end
//promise1
//promise2
//setTimeout

宏任务与微任务

(1)主线程闲置,执行任务队列宏任务,当前 JS 代码进入主线程被 JS 引擎执行。按序执行,先输出script start

(2) 接着执行 Promise.resolve(1),该回调进入微任务

(3)执行 setTimeout,回调进入宏任务(这个宏任务是下一个宏任务,而不是当前宏任务)

(4)执行Promise.resolve(2),该回调进入微任务

(5)继续执行,输出script end,当前宏任务执行完毕。检测微任务列表

(6)执行微任务列表,按顺序输出 promise1 promise2

(7)当前微任务列表为空,渲染 DOM 后执行下一宏任务,即 setTimeout 回调函数,输出 setTimeout

例子2:

console.log('script start');
Promise.resolve().then(function() {
  	setTimeout(function() {
      console.log('setTimeout1');
    }, 0);
}).then(function() {
  console.log('promise1');
});

setTimeout(function() {
	console.log('setTimeout2')
	Promise.resolve().then(function(){
		console.log('promise2');
	})
},0)
console.log('script end');

//输出:
//script start
//script end
//promise1
//setTimeout2
//promise2
//setTimeout1

宏任务与微任务

(1)执行当前宏任务,输出 script start

(2)执行 Promise.resolve(1),回调(内含函数 setTimeout(1))进入微任务

(3)执行 setTimeout(2),回调(内含函数 Promise.resolve(2)) 进入宏任务

(4)输出 script end,当前宏任务结束。

(5)宏任务结束,查看微任务队列,当前微任务是 Promise.resolve(1) 回调,执行回调里面的 setTimeout(1),定时器回调进入宏任务,接着输出 promise1,当前微任务为空

(6)当前微任务为空,执行下一宏任务。当前宏任务是 setTimeout(2)回调,输出 setTimeout2,执行回调里面的 Promise.resolve(2),回调进入微任务,当前宏任务结束

(7)当前宏任务结束,执行微任务。输出 Promise2,微任务为空。

(8)执行下一宏任务,setTimeout1 回调输出 setTimeout1

可以画图帮助理解,弄清楚上面这个流程基本上就理解这个知识了

当然有时候有些浏览器并不是这么表现,例如会把 Promise.resolve 当成当成宏任务处理,但是都是特殊情况,因为这样消耗会变很大,如上面流程显示才是最标准的。

最后结合上并发模型博客里讲的定时器线程再来一个例子仅供玩耍

console.log('script start');
Promise.resolve().then(function() {
  	setTimeout(function() {
      console.log('setTimeout1');
    }, 0);
}).then(function() {
  console.log('promise1');
});

setTimeout(function() {
	console.log('setTimeout2')
	Promise.resolve().then(function(){
		console.log('promise2');
	})
},3000)
console.log('script end');

//输出:
//script start
//script end
//promise1
//setTimeout1
//setTimeout2
//promise2