1、常见的定时器
- setTimeOut
- setInterval
- setImmediate (Node特有)
- process.nextTick(Node特有)
以及在代码执行次序的面试题中常出现的 Promise
对象。
2、代码执行次序
首先,任务分为同步任务 和 异步任务,并且同步任务 总是早于 异步任务执行。
其次,异步任务分为追加在本轮循环的异步任务 和 追加在次轮循环的异步任务,并且追加在本轮循环的异步任务 总是早于 追加在次轮循环的异步任务执行。
2.1 常见定时器回调执行时机
- process.nextTick的回调函数 —— 异步任务,追加在本轮循环
- setTimeOut的回调函数 —— 异步任务,追加在次轮循环
- setInterval的回调函数 —— 异步任务,追加在次轮循环
- setImmediate的回调函数 —— 异步任务,追加在次轮循环
(1)process.nextTick()
该方法的回调是在本轮循环中执行,且是所有异步任务里面最先执行的,即执行完同步就开始执行 process.nextTick
的任务队列。
(2)setTimeOut() 在 timers 阶段执行。
(3)setImmediate() check 阶段执行。
注意:原则上 timers 阶段在 check 阶段之前,
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
故,上述代码中,setTimeOut
会早于 setImmediate
,即先输出 1 后输出 2。但在实际执行的时候,有可能先输出 2 后输出 1。
这是因为在Node中 setTimeout
的第二个参数默认为0。但是实际上,Node 做不到0毫秒,最少也需要1毫秒,也就是说,setTimeout(f, 0)
等同于 setTimeout(f, 1)
。实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况。如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数。
2.2 Promise对象各种回调执行时机
(1)新建Promise对象
Promise新建后就会立即执行,即
new Promise(callback)
当代码执行到该行时,callback将立即执行。其中 callback
中调用 resolve
或 reject
并不会终结 callback
的执行,即 resolve
或 reject
的后续代码依然会执行。
(2)Promise.prototype.then() 和 Promise.prototype.catch() 方法
resolve
和 reject
的Promise会被追加在本轮循环,即
new Promise(callback).then/catch
then
或 catch
中的代码将在本轮循环的末尾执行,先于次轮循环。
(3)Promise.resolve()
该方法参数可以分为四种情况,但都遵循上述两种执行次序。
3、事件循环的六个阶段
- timers
- I/O callbacks
- idle, prepare
- poll
- check
- close callbacks
4、示例
setTimeout(() => console.log(1));
setImmediate(() => console.log(2));
process.nextTick(() => console.log(3));
new Promise((res, rej) => {console.log(4);res()}).then(() => console.log(5))
Promise.resolve().then(() => console.log(6));
(() => console.log(7))();
运行结果:
4 7 3 5 6 1 2