EventLoop事件循环:从入门到放弃

什么叫事件循环?

简单来说就是:主线程执行完同步任务后,依次执行异步任务的微任务队列宏任务队列,这个过程是循环不断的,整个的这种运行机制称为事件循环(EventLoop)。

微任务(micro task):process.nextTick、await、promise.then
宏任务(macro task):setTimeout、setInterval、setImmediate

微任务、宏任务的区别

执行完同步任务后,会有异步任务的微任务队列和宏任务队列。微任务先执行,然后是宏任务。

1
2
3
4
5
6
7
8
9
10
setTimeout(() => {
console.log(1)
}, 0)

Promise.resolve().then(() => {
console.log(2)
})

console.log(3)
//3 2 1

Promise 以及链式调用 then 的执行方式

Promise 的 executor 是一个同步函数,立即执行的函数,因此它应该是和当前的任务一起同步执行的。而 Promise 的链式调用 then,每次都会在内部生成一个新的 Promise,然后执行 then,在执行的过程中不断向微任务队列推入新的任务,因此直至微任务队列清空后才会执行宏任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setTimeout(() => {
console.log(1)
}, 0)

let a = new Promise((resolve) => {
console.log(2)
resolve()
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})

console.log(5)
//2 5 3 4 1

链式调用 then 每次都生成一个新的 Promise,也就是说每个 then 的回调方法属于同一个微任务队列。

嵌套式的 Promise

遇到嵌套式的 Promise 不要慌,首先要心中有一个队列,能够将这些任务放到相对应的队列之中。

在 then 的回调函数中创建一个新的 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Promise((resolve, reject) => {
console.log("promise1")
resolve()
}).then(() => {
console.log("then11")
new Promise((resolve, reject) => {
console.log("promise2")
resolve()
}).then(() => {
console.log("then21")
}).then(() => {
console.log("then22")
})
}).then(() => {
console.log("then12")
})
//promise1 then11 promise2 then21 then12 then22

第一轮

  • current task:Promise 的 executor 立即执行,最先输出promise1
  • micro task queue:[then11]

第二轮

  • current task:执行 then11 的回调函数,输出then11,紧接着第二个 Promise 的 executor 立即执行,输出promise2
  • micro task queue:[then21, then12]

第三轮

  • current task:执行 then21 的回调函数,输出then21,执行 then12 的回调函数,输出then12
  • micro task queue:[then22]

第三轮

  • current task:执行 then22 的回调函数,输出then22

链式调用 then 可能会被 Evenloop 中其他的任务插队:then12 的回调函数 在 then22 之前执行,若改为分别调用 then 则不会被插队

在 then 的回调函数中返回一个新的 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Promise((resolve, reject) => {
console.log("promise1")
resolve()
}).then(() => {
console.log("then11")
return new Promise((resolve, reject) => {
console.log("promise2")
resolve()
}).then(() => {
console.log("then21")
}).then(() => {
console.log("then22")
})
}).then(() => {
console.log("then12")
})
//promise1 then11 promise2 then21 then22 then12

这里的 then12 相当于是挂在新 Promise 的最后一个 then 的返回值上。

多个 Promise 的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
new Promise((resolve, reject) => {
console.log("promise1")
resolve()
}).then(() => {
console.log("then11")
new Promise((resolve, reject) => {
console.log("promise2")
resolve()
}).then(() => {
console.log("then21")
}).then(() => {
console.log("then22")
})
}).then(() => {
console.log("then12")
})

new Promise((resolve, reject) => {
console.log("promise3")
resolve()
}).then(() => {
console.log("then3")
})
//promise1 promise3 then11 promise2 then3 then21 then12 then22

第一轮

  • current task:输出promise1promise3
  • micro task queue:[then11, then3]

第二轮

  • current task:执行 then11 的回调函数,输出then11,输出promise2,执行 then3 的回调函数,输出then3
  • micro task queue:[then21, then12]

第三轮

  • current task:执行 then21 的回调函数,输出then21,执行 then12 的回调函数,输出then12
  • micro task queue:[then22]

第三轮

  • current task:执行 then22 的回调函数,输出then22

async/await 对 Eventloop 的影响

async/await 仅仅影响的是函数内的执行,而不会影响到函数体外的执行顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}

async function async2() {
console.log('async2');
}

console.log("script start");

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

async1();

new Promise(function (resolve) {
console.log("promise");
resolve();
}).then(function () {
console.log("then");
});

console.log('script end');
//script start, async1 start, async2, promise, script end, async1 end, then, settimeout

await async2()相当于一个 Promise,后面的语句相当于 then 的回调函数

NodeJS 事件 + Promise + async/await + setImmediate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}

async function async2() {
console.log('async2');
}

console.log("script start");

setTimeout(function () {
console.log("setTimeout");
});

async1()

new Promise(function (resolve) {
console.log("promise");
resolve();
}).then(function () {
console.log("then");
});

setImmediate(() => {
console.log("setImmediate")
})

process.nextTick(() => {
console.log("process")
})

console.log('script end');
//script start, async1 start, async2, promise, script end, process, async1 end, then, setTimeout, setImmediate

第一轮

  • current task:输出script startasync1 startasync2promisescript end
  • micro task queue:[await, then, nextTick]
  • macro task queue:[setTimeout, setImmediate]

第二轮

  • current task:输出processasync1 endthensetTimeoutsetImmediate

小结

在处理一段 EvenLoop 执行顺序的时候:

  • 第一:确定微任务,宏任务
  • 第二:解析“拦路虎”,出现 async/await 不要慌,它们只在标记的函数中能够作威作福,出了这个函数还是跟着大部队的潮流。
  • 第三:根据 Promise 中 then 的调用方式不同做出不同的判断,是链式还是分别调用。
  • 最后:优先级:process.nextTick > await > promise.then
0%