从promise、process.nextTick、setTimeout出发,谈Event Loop中的Job queue

一、问题的引出

  1. event loop是指主线程从“任务队列”中循环读取任务

例1:

setTimeout(
	function(){
		console.log(1);
	}
},0);
console.log(2);
//输出2,1

在上述例子中首先执行主线程中的同步任务,当主线程任务执行完毕后,再从event loop中读取任务,因此先输出2,再输出1。

  • JavaScript 是单线程执行的,也就是无法同时执行多段代码,当某一段代码正在执行时,所有后续的任务都必须等待,形成一个队列,一旦当前任务执行完毕,再从队列中取出下一个任务。这也常被称为 “阻塞式执行”。
  • 一次鼠标点击,或计时器到达时间点,或 Ajax 请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。
  • 假如当前 JavaScript 进程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。
  • setTimeout并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。
  1. event loop读取任务的先后顺序,取决任务队列(Job queue)中对于不同任务读取规则的限定

例2:

setTimeout(function () {
  console.log(3);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});

console.log(1);
//输出为  1  2 3

先输出1,是因为同步任务在主线程中优先执行,但这里的问题是setTimeout和Promise.then任务的执行优先级是如何定义的。

二、Job queue中的执行顺序

  1. 在Job queue中的队列分为两种类型:macro-taskmicroTask

举例来看执行顺序的规定,设:
macro-task队列包含任务: a1, a2 , a3
micro-task队列包含任务: b1, b2 , b3

执行顺序为:首先执行marco-task队列开头的任务,也就是 a1 任务,执行完毕后,在执行micro-task队列里的所有任务,也就是依次执行b1, b2 , b3,执行完后清空micro-task中的任务,接着执行marco-task中的第二个任务,依次循环。

  1. 了解完macro-task和micro-task两种队列的执行顺序之后,接着来看真实场景下这两种类型的队列里真正包含的任务(以node V8引擎为例)

在node V8中,这两种类型的真实任务顺序如下所示:

  • macro-task队列真实包含任务:
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
  • micro-task队列真实包含任务:
process.nextTick, Promises, Object.observe, MutationObserver
  • 由此得到的执行顺序应为:
script(主程序代码)—>process.nextTick—>Promises...——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering
  • 在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs

三、 真实环境中执行顺序的举例

例3:setTimeout和promise

setTimeout(function () {
  console.log(3);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});

console.log(1);

先以第1小节的例子为例,这里遵循的顺序为:

script(主程序代码)——>promise——>setTimeout

对应的输出依次为:1 ——>2——>3

例4:process.nextTick和promise、setTimeout

setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
   console.log(2);
   resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);
//输出2,6,5,3,4,1

需注意:在定义promise时,promise构造部分是同步执行的

首先分析Job queue的执行顺序:

script(主程序代码)——>process.nextTick——>promise——>setTimeout

I) 主体部分: 定义promise的构造部分是同步的
因此先输出2 ,主体部分再输出6(同步情况下,就是严格按照定义的先后顺序)

II)process.nextTick: 输出5

III)promise: 这里的promise部分,严格的说其实是promise.then部分,输出的是3,4

IV) setTimeout : 最后输出1

综合的执行顺序: 2——>6——>5——>3——>4——>1

例5:更复杂的例子

setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
   console.log(2);
   setTimeout(function(){resolve()},0)
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);
//输出的是  2 6 5 1 3 4

例5与例4的区别在于promise的构造中,没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,因此3,4最后输出。

你可能感兴趣的:(js,前端碎片)