# ccforward/cc

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

# 44.理解事件循环一(浅析) #47

Open
opened this Issue Nov 3, 2016 · 19 comments

Projects
None yet
Owner

# Node.js 事件循环一: 浅析

SSD读取很快，但和CPU处理指令的速度比起来也不在一个数量级上，而且网络上一个数据包来回的时间更慢：

## 栈 Stack

```function fire() {
const result = sumSqrt(3, 4)
console.log(result);
}
function sumSqrt(x, y) {
const s1 = square(x)
const s2 = square(y)
const sum = s1 + s2;
return Math.sqrt(sum)
}
function square(x) {
return x * x;
}

fire()```

`fire` 调用 `sumSqrt` 函数 参数为3和4

`square` 执行结束返回时，从 stack 中弹出，并将返回值赋值给 s1
s1加入到 sumSqrt 的 stack frame 中

## 事件循环

```'use strict'

const express = require('express')
const superagent = require('superagent')
const app = express()

app.get('/', getArticle)

function getArticle(req, res) {
fetchArticle(req, res)
print()
}

const aids = [4564824, 4506868, 4767667, 4856099, 7456996];

function fetchArticle(req, res) {
const aid = aids[Math.floor(Math.random() * aids.length)]
superagent.get(`http://news-at.zhihu.com/api/4/news/\${aid}`)
.end((err, res) => {
if(err) {
console.log('error ......');
return res.status(500).send('an error ......')
}
const article = res.body
res.send(article)
console.log('Got an article')
})

console.log('Now is fetching an article')
}

function print(){
console.log('Print something')
}

app.listen('5000')
```

``````Now is fetching an article

Print something

Got an article
``````

### 任务队列

javascript 是单线程事件驱动的语言，那我们可以给时间添加监听器，当事件触发时，监听器就能执行回调函数。

1. express 给 request 事件注册了一个 handler，并且当请求到达路径 '/' 时来触发handler
2. 调过各个函数并且在端口 5000 上启动监听
3. stack 为空，等待 `request` 事件触发
4. 根据传入的请求，事件触发，express 调用之前提供的函数 `getArticle`
5. `getArticle` 压入(push) stack
6. `fetchArticle` 被调用 同时压入 stack
7. `Math.floor``Math.random` 被调用压入 stack 然后再 弹出(pop), 从 aids 里面取出的一个值被赋值给变量 aid
8. `superagent.get` 被执行，参数为 `'http://news-at.zhihu.com/api/4/news/\${aid}'` ,并且回调函数注册给了 `end` 事件
9. `http://news-at.zhihu.com/api/4/news/\${aid}` 的HTTP请求被发送到后台线程，然后函数继续往下执行
10. `'Now is fetching an article'` 打印在 console 中。 函数 `fetchArticle` 返回
11. `print` 函数被调用, `'Print something'` 打印在 console 中
12. 函数 `getArticle` 返回，并从 stack 中弹出， stack 为空
13. 等待 `http://news-at.zhihu.com/api/4/news/\${aid}` 发送相应信息
14. 响应信息到达，`end` 事件被触发
15. 注册给 `end` 事件的匿名回调函数被执行，这个匿名函数和他闭包中的所有变量压入 stack，这意味着这个匿名函数可以访问并修改 `express`, `superagent`, `app`, `aids`, `req`, `res`, `aid` 的值以及之前所有已经定义的函数
16. 函数 `res.send()` 伴随着 200 或 500 的状态码被执行，但同时又被放入到后台线程中，因此 响应流 不会阻塞我们函数的执行。匿名函数也被 pop 出 stack。

## Microtasks Macrotasks

microtasks:

• process.nextTick
• promise
• Object.observe

macrotasks:

• setTimeout
• setInterval
• setImmediate
• I/O

```console.log('start')

const interval = setInterval(() => {
console.log('setInterval')
}, 0)

setTimeout(() => {
console.log('setTimeout 1')
Promise.resolve()
.then(() => {
console.log('promise 3')
})
.then(() => {
console.log('promise 4')
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve()
.then(() => {
console.log('promise 5')
})
.then(() => {
console.log('promise 6')
})
.then(() => {
clearInterval(interval)
})
}, 0)
})
}, 0)

Promise.resolve()
.then(() => {
console.log('promise 1')
})
.then(() => {
console.log('promise 2')
})
```

``````start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
``````

#### Cycle 1

1) `setInterval` 被列为 task

2) `setTimeout 1` 被列为 task

3) `Promise.resolve 1` 中两个 `then` 被列为 microtask

4) stack 清空 microtasks 执行

#### Cycle 2

5) microtasks 队列清空 `setInteval` 的回调可以执行。另一个 `setInterval` 被列为 task , 位于 `setTimeout 1` 后面

#### Cycle 3

6) microtask 队列清空，`setTimeout 1` 的回调可以执行，`promise 3``promise 4` 被列为 microtasks

7) `promise 3``promise 4` 执行。 `setTimeout 2` 被列为 task

#### Cycle 4

8) microtask 队列清空 `setInteval` 的回调可以执行。然后另一个 `setInterval` 被列为 task ，位于 `setTimeout 2` 后面

9) `setTimeout 2` 的回调执行， `promise 5``promise 6` 被列为 microtasks

``````...
setTimeout 2
setInterval
promise 5
promise 6
``````

## Last

 great!

### zhanglun commented Nov 4, 2016

 好奇，文中的图片 左边的编辑器师怎么做的
Owner

### ccforward commented Nov 4, 2016

 @zhanglun 左边其实就是 sublime 截的图

### shouhe commented Nov 4, 2016

 好文 正好时间循环机制不是很了解

### liwenlong commented Nov 4, 2016

 写的很详细，赞。 希望以后加点，复杂的一些流程流程中转的话，node是如何处理的~

Open

Open

Open

### Thinking80s commented Mar 20, 2017

 学习了事件循环

Open

### ihaichao commented May 14, 2017

 为什么在 Cycle2 中 setInterval 和 setTimeout 同样是 macrotask，但是先执行 setInterval 呢？

### bloody-ux commented Jun 6, 2017

 “microtasks 队列清空” 这个过程应该在每个cycle的结尾，更加严格讲是每个task执行完毕后从task队列中移除。

### Aaaaaaaty commented Jul 21, 2017

 这个sublime主题很好看，可以搜到么
Owner

### ccforward commented Jul 21, 2017

 @Aaaaaaaty 主题是我自己改的颜色

### Aaaaaaaty commented Jul 21, 2017

 @ccforward 啊这样 谢谢啦 文章好nice 持续关注中~

### Mrcooder commented Jul 22, 2017

 我有一个问题, Promise.then 里的 callback 是 microtask 吗? 这个不应该是 macrotask 吗?

### Aaaaaaaty commented Aug 8, 2017 • edited

 @mrcodehang 是microtask，可以参考这篇文章，promise都应该是microtask,在一个次循环后串行打印结果，只是有的环境将promise callback解释为macrotask而不是microtask导致打印顺序出了问题。至于为什么环境之间对它的解释不一样，这似乎涉及到ECMA和HTML之间的一些标准界定了=。=毕竟厂商们都是自己按照标准来撸一套环境

Open

### ajhsu commented Apr 15, 2018

 @ccforward 謝謝樓主分享如此詳盡的比較與分析。 我了解到 `macrotask === task !== microtask`，但有個問題想請教一下樓主： 目前我除了可以在這份文件 找到 `macro-task `這個名詞以外，似乎在其他正式文件 (如 WHATWG) 裡頭，還是以 `task` 稱呼之，所以想請教下， `macrotask` 這個名詞的起源為何呢？

### Jokcy commented Apr 19, 2018

 这段代码在chrome console中执行，第二个setInterval输出了两次，是什么情况？

### lawpachi commented Apr 19, 2018

 请教下作者 async/await 属不属于microtasks

### Mrcooder commented Apr 20, 2018

 @lawpachi async/await 相当于是 Promise。应该是属于 microtasks

### yubaoquan referenced this issue Nov 26, 2018

Open

#### Event Loop #19

to join this conversation on GitHub. Already have an account? Sign in to comment