本期精读的文章是：

[How JavaScript works: Event loop and the rise of Async programming + 5 ways to better coding with async/await](https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5)

# 1 引言

我为什么要选这篇文章呢？

sessionstack 最近接连发了好几篇文章, 深入探讨 JS, 以及  JS 中一些内部原理. 文中也讲到了, 伴随深入了解 JS 中的一些工作原理, 才有可能写出更好的代码和程序.

而 JS 中 Event Loop, 我的感觉就像 JS 中的一门内科, 我们平时只注意外科创伤，却忽视了内科问题往往容易莫名其妙的生病。了解 JS  Event Loop 的原理，对 `setTimeout` `Promise` 这种基础概念不再浮在表层，可以写出更可靠的代码，如果你是前端新人，不要总是因为这个问题挂在一面 :p。

# 2 内容概要

从前我对 Event Loop 的理解也并不透彻，通过仔细阅读此文后， Event Loop 、宿主环境、js 线程三者之间关系更加透明了，希望读者读完后也能有所体会。

文中 Promise、async/await 部分就忽略了，本篇重点介绍 Event Loop 。

### Event Loop 与 Call Stack、Web APIs 之间的关系

![Event Loop](https://img.alicdn.com/imgextra/i1/O1CN01kfYH061VI7vfO7App_!!6000000002629-2-tps-1024-768.png)

原文通过 16 个图表达了 5 行代码的执行过程，太长就只贴第一张图了。

Call Stack 是调用栈，Event Loop 就是本期的主角 - 事件循环，Web APIs 泛指宿主环境，比如 nodejs 中的 c++，前端中的浏览器。

任何同步的代码都只存在于 Call Stack 中，遵循先进后出，后进先出的规则，也就是只有异步的代码（不一定是回调）才会进入 Event Loop 中，哪些是异步代码呢？比如：

```javascript
setTimeout()
setInterval()
Promise.resolve().then()
fetch().then()
```

所有这些异步代码在执行时，都不会进入 Call Stack，而是进入 Event Loop 队列，此时 JS 主线程执行完毕后，且异步时机到了，就会将异步回调中的代码推入 Call Stack 执行。

而控制异步什么时机开始执行，是由宿主环境决定的，因为此时 js 主线程已经调用完毕，除非 Event Loop 队列有内容，推送到 Call Stack 中，否则 js 引擎也不会再执行任何代码。比如通过 `fetch` 发送请求，当 js 调用浏览器发送请求后，直到浏览器主动告诉 js 请求完成了，期间 js 是无法干预任何的。

最终效果如下 gif 图所示：

![Event Loop](https://img.alicdn.com/imgextra/i1/O1CN01AM2BOf1XF2UYPbGiF_!!6000000002893-2-tps-808-606.png)

### Microtask 与 Macrotask

Event Loop 处理异步的方式也分两种，分别是 `setTimeout` 之流的 `Macrotask`，与 `Promise` 之流的 `Microtask`。

异步队列是周而复始循环执行的，可以看作是二维数组：横排是一个队列中的每一个函数，纵排是每一个队列。

`Macrotask` 的方式是将执行函数添加到新的纵排，而 `Microtask` 将执行函数添加到当前执行到队列的横排，因此 `Microtask` 方式的插入是轻量的，最快被执行到的。

# 3 精读

Event Loop 内容不多，内容概要部分已经讲的比较彻底了，原文最后扯到了 Promise, async/await 的用法和注意点，不然是不会这么长的。

我最近写了一些 [dob-react tests](https://github.com/dobjs/dob-react/blob/master/src/tests/index.test.tsx) 测试文件，发现 `componentWillMount` 函数在 `Microtask` 时机 `setState` 不会触发 rerender:

```typescript
class Hello extends React.Component {
	async componentWillMount() {
  	await immediate(()=>{
    	this.setState({a:1})
    })
  }

  render() { /**/ }
}
```

这种 immediate 函数的写法只会 render 一次：

```typescript
function immediate(fn) {
    return new Promise(resolve => {
        fn()
        resolve()
    });
}
```

在线 Demo：http://jsfiddle.net/69z2wepo/90440/

如果再套一层 setTimeout，哪怕是一层 Promise, 就会 render 两次：

```typescript
function immediate(fn) {
    return new Promise(resolve => Promise.resolve().then(() => {
        fn()
        resolve()
    }));
}
```

在线 Demo：http://jsfiddle.net/69z2wepo/90441/

ps: 感谢读者们的回复，其实第一个 immediate 函数根本就写错了，执行 fn 的时机是同步的，还是老老实实的使用 Promise().resolve().then() 吧。

# 4 总结

理解了事件循环之后，才是第一步，比如我就对 React 的生命周期中异步 setState 合并机制时而生效，时而不生效抱有疑问，所以想要写好稳健的业务代码还是挺难的，首先要理解这种 “内科” 知识，其次要读懂 react 源码，最后你还要保证不会忘。

> 讨论地址是：[精读《Javascript 事件循环与异步》 · Issue #41 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/41)

> 如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。
