js 深入学习 -异步与性能

js 深入学习 -异步与性能

You-Dont-Know-JS(你不知道的 js 这本书的开源版本)

You-Dont-Know-JS(你不知道的 js 这本书的开源版本)

github 国内翻译

https://github.com/JoeHetfield/You-Dont-Know-JS

掘金中文

https://juejin.cn/post/6844903478813261831

github 原帖

https://github.com/getify/You-Dont-Know-JS

简述

一个 JavaScript 程序总是被打断为两个或更多的代码块儿,第一个代码块儿 现在 运行,下一个代码块儿 稍后 运行,来响应一个事件。虽然程序是一块儿一块儿地被执行的,但它们都共享相同的程序作用域和状态,所以对状态的每次修改都是在前一个状态之上的

不论何时有事件要运行,事件轮询 将运行至队列为空。事件轮询的每次迭代称为一个“tick”。用户交互,IO,和定时器会将事件在事件队列中排队。

Jobs

工作队列,就是插入到这次事件循环的末尾。

jobs 就是 promise 的雏形。 说白了就是 微任务,同步任务执行完了,才执行 微任务。

而 setTimeout 等属于 宏任务, 是下一次事件循环才执行,也就是最后执行。

协作

就是处理千万级,大量的数据的时候,一个执行栈会耗费数秒,甚至上分钟时,导致用户 ui 都卡顿的时候。

我们可以将其拆分为多个 异步执行栈,让他分流执行。 setTimeout(fn, 0) 是最佳实践, 我们使用setTimeout(..0)(黑科技)来异步排程,基本上它的意思是“将这个函数贴在事件轮询队列的末尾”。

注意: 从技术上讲,setTimeout(..0)没有直接将一条记录插入事件轮询队列。计时器将会在下一个运行机会将事件插入。比如,两个连续的setTimeout(..0)调用不会严格保证以调用的顺序被处理,所以我们可能看到各种时间偏移的情况,使这样的事件的顺序是不可预知的。在 Node.js 中,一个相似的方式是process.nextTick(..)。不管那将会有多方便(而且通常性能更好),(还)没有一个直接的方法可以横跨所有环境来保证异步事件顺序。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var res = [];

// `response(..)`从Ajax调用收到一个结果数组
function response(data) {
// 连接到既存的`res`数组上
res = res.concat(
// 制造一个新的变形过的数组,所有的`data`值都翻倍
data.map(function (val) {
return val * 2;
})
);
}

// ajax(..) 是某个包中任意的Ajax函数
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);

如果http://some.url.1首先返回它的结果,整个结果列表将会一次性映射进res。如果只有几千或更少的结果记录,一般来说不是什么大事。但假如有 1 千万个记录,那么就可能会花一段时间运行(在强大的笔记本电脑上花几秒钟,在移动设备上花的时间长得多,等等)。

当这样的“处理”运行时,页面上没有任何事情可以发生,包括不能有另一个**response(..)**调用,不能有 UI 更新,甚至不能有用户事件比如滚动,打字,按钮点击等。非常痛苦。

所以,为了制造协作性更强、更友好而且不独占事件轮询队列的并发系统,你可以在一个异步批处理中处理这些结果,在批处理的每一步都“让出”事件轮询来让其他等待的事件发生。

这是一个非常简单的方法:

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
var res = [];

// `response(..)`从Ajax调用收到一个结果数组
function response(data) {
// 我们一次只处理1000件
var chunk = data.splice(0, 1000);

// 连接到既存的`res`数组上
res = res.concat(
// 制造一个新的变形过的数组,所有的`data`值都翻倍
chunk.map(function (val) {
return val * 2;
})
);

// 还有东西要处理吗?
if (data.length > 0) {
// 异步规划下一个批处理
setTimeout(function () {
response(data);
}, 0);
}
}

// ajax(..) 是某个包中任意的Ajax函数
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);

我们以每次最大 1000 件作为一个块儿处理数据。这样,我们保证每个“进程”都是短时间运行的,即便这意味着会有许多后续的“进程”,在事件轮询队列上的穿插将会给我们一个响应性(性能)强得多的网站/应用程序。

当然,我们没有对任何这些“进程”的顺序进行互动协调,所以在res中的结果的顺序是不可预知的。如果要求顺序,你需要使用我们之前讨论的互动技术,或者在本书后续章节中介绍的其他技术。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!