You Don't Know JS: Async & Performance(第一章, 异步:now & later)

Chapter 1: Asynchrony: Now & Later

在一门语言中,比如JavaScript, 最重要但仍然常常被误解的编程部分是如何在一个完整的时间周期表示和操作程序行为。

这是关于当你的程序正在部分运行,其他部分等待运行。--这之间的gap。

mind the gap!(比如在subway door and the platform)

异步编程就是这个核心: now and later parts of your program

在JS的发展初级, callback function足够用了。但是JS继续在scope和complexity方向成长,为了满足不断扩展的要求(作为第一类编程语言,它运行在浏览器,服务器,已经每个它们之间的设备),开发者需要更强大的并且合理的功能。

后几章我们会探索各种异步的JS技术。

但此时我们将不得不更深度地理解什么是异步asynchrony, 它如何在JS操作!


A Program in Chunks

可以把JS程序 写在一个.js文件内,不过程序是由不同的部分组成,有的部分现在执行,有的则等待执行。

常见的chunk单位是函数。

比如发送请求并⌛️处理响应的数据。这之间有一个gap, 最简单的等待的方式是使用一个函数,即回调函数:

ajax( "http://some.url.1", function myCallbackFunction(data){

    console.log( data ); // Yay, I gots me some `data`!

} );

再看一个:

function now() {
return 21;
} function later() {
answer = answer * 2;                //黄色部分是later
console.log( "Meaning of life:", answer );
} var answer = now(); setTimeout( later, 1000 ); // Meaning of life: 42

分为立即执行的now chunk, 和1000毫秒(1秒)后的later chunk

setTimeout()建立了一个事件(a timeout)在1秒之后发生。所以later()函数在1秒之后执行。

当你把一段代码放入一个函数并直到它被执行,用来响应某个事件,你正在创建一个later chunk。

因此asynchrony来到了你的程序!

Async Console

注意console.log也是异步的,不同的浏览器consol I/O可能不同导致不同的输出console.log(..)。

所以在debugging时,需要当心!!

var a = {
index: 1
}; // later
console.log( a ); // ?? // even later
a.index++; 这个例子: 有可能console.log(a) 在a.index++执行后,才执行!

注意:debug最好是用断点来代替console.log输出。 或者把问题对象转化为JSON格式(JSON.stringify)


Event Loop

尽管JS允许异步代码,但直到ES6,JS本身没有任何直接的异步概念内建在JS。

The JS engine itself has never done anything more than execute a single chunk of your program at any given moment, when asked to.

The JS engine doesn't run in isolation. It runs inside a hosting environment, which is for most developers the typical web browser.

Over the last several years (but by no means exclusively), JS has expanded beyond the browser into other environments, such as servers, via things like Node.js.

In fact, JavaScript gets embedded into all kinds of devices these days, from robots to lightbulbs.

But the one common "thread" (that's a not-so-subtle asynchronous joke, for what it's worth) of all these environments is that they have a mechanism in them that handles executing multiple chunks of your program over time, at each moment invoking the JS engine, called the "event loop."

so, 比如,当你的JS程序发出Ajax请求向服务器取数据,你在一个函数内建立响应代码(callback),并且JS engine 告诉hosting environment:“喂,我将暂停执行,但是当你完成网络请求,并有数据,请调用回调函数”。

浏览器于是建立监听从网络来的响应,并当它有something给你,它根据时间表安排回调函数,把它插入event loop中执行。

什么是event loop?

伪代码演示:

// `eventLoop` is an array that acts as a queue队列,先排队的先办理
var eventLoop = [ ];
var event; // keep going "forever"
while (true) {
// perform a "tick"
if (eventLoop.length > 0) {
// get the next event in the queue
event = eventLoop.shift(); // now, execute the next event
try {
event();
}
catch (err) {
reportError(err);
}
}
}

持续的运行♻️, 每次迭代(iteration重复)循环被称为,tick!

每一个tick, 如果一个事件在queue中等待,执行它,拿出队列。这些事件就是你的函数回调。

需要重点⚠️:

setTimeout()不会把你的回调函数放到event loop queue中。它只是建立一个timer。当timer expires, 环境会把你的回调放入event loop,等待, 当tick到它,就会执行它。

所以,真实的回调函数发生的时间比setTimeout()设置的时间要多一个等待时间。

⚠️!

ES6改变了event loop queue被管理的模式。ES6明确了event loop 如何工作。这意味着技术上它属于JS engine 的 范围, 而不是hosting environment。

这么做的主要原因在Promises内有介绍。(见第3章),因为需要直接的管理在event loop queue的时间表操作。


Parallel Threading 平行线程

async和parallel是 2个不同的事情。

记住,async是关于现在后之后的这个缺口gap。而parallel是关于事情能够被同步发生。

最普通的parallel计算工具是processes 和 threads。Processes and threads 可以独立地执行,也可以同步地执行。on separate processors, or even separate computers, 但是多个线程可以共享一个单独进程的内存

An event loop, by contrast, breaks its work into tasks and executes them in serial, disallowing parallel access and changes to shared memory. Parallelism and "serialism" can 共存 in the form of cooperating event loops in separate threads.

一个event loop, 不允许parallel access 和改变共享的内存。平行和序列可以在event loop这个合作的event loops形式下在各自的线程内同时存在。

我的理解:

同时执行2个线程,那么共享的内存的数据被这2个线程使用会导致数据混乱,造成结果的不确定。

所以,JS不会共享data across 线程。

Run-to-Completion

JS是单线程的,所以可能是先执行foo,等foo执行完成后,再执行bar。也肯能相反。

var a = 20;

function foo() {
a = a + 1;
} function bar() {
a = a * 2;
} // ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );

这样的不确定问题无法解决!

因此,ES6介绍了一个thing,可以指定先执行哪个。

Concurrency并发

an example of event, actions etc happening at the same time!

让我们想象一个网页,它显示一个更新状态的列表,当用户滚动列表时,加载这个列表。为了让这样的功能实现,至少需要2个独立的'processes'被同步地执行。

当用户滚动页面到底部时, 激活第一个‘process1’响应 onscroll事件(发出Ajax请求,请求新的list内容)。

第二个‘process2’将会接收Ajax 响应(用于把数据渲染到网页)。

当用户的滚动足够快,你会看到2个以上的onscroll事件在刚完成第一个响应的返回和处理时就fire了。

比如: 滚动请求4和5,6,发生的足够块,以至于响应4和滚动请求6同时激活。

onscroll, request 1
onscroll, request 2 response 1
onscroll, request 3 response 2
response 3
onscroll, request 4
onscroll, request 5
onscroll, request 6 response 4
onscroll, request 7
response 6
response 5
response 7

并发就是2个或更多的‘processes’在相同的时间段同步的执行,不考虑是否他们的内部的event操作在平行(在相同的一瞬间)发生。你可以认为并发是在‘process’层次的平行(task-level),不是操作层次的平行 (operation-level)。

本例子:

process1在请求1开始,在请求7结束。

process2在响应1开始,在响应7结束。它们是同步执行的(并发concurrency)。

因此就造成了以下可能:

一个滚动时间和一个Ajax响应事件可能在相同的时刻等待被处理。比如请求2和响应1。

但是,JS是单线程的,在event loop queue中,只能一次处理一个事件。

这就造成了不确定性nondeterminism。

于是event loop queue可能是这样排队的:(也可能是另外的排序)

onscroll, request 1   <--- Process 1 starts
onscroll, request 2
response 1 <--- Process 2 starts
onscroll, request 3
response 2
response 3
onscroll, request 4
onscroll, request 5
onscroll, request 6
response 4
onscroll, request 7 <--- Process 1 finishes
response 6            //出错❌了!!!
response 5
response 7 <--- Process 2 finishes

2个process并发运行(task-level parallel),但它们内部的独立事件需要在event loop queue中排队处理。

Noninteracting 不交互

如果两个'process'不会产生交互(互相不干扰),则nondeterminism非确定性可以完全接受。

var res = {};

function foo(results) {
res.foo = results;
} function bar(results) {
res.bar = results;
} // ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );

foo, bar互不干扰,谁在event loop queue的前面,无所谓。

Interaction

更常见的,并发"processes"是必须要交互的, 非直接的通过作用域和/或者 DOM。

当如此交互发生 , 你需要coordinate协调这些交互,防止"race conditions",

上一篇:Linux USB驱动


下一篇:20145308刘昊阳 《Java程序设计》第8周学习总结