You Don't Know JS读书笔记之Async

Chapter 1:Asynchrony: Now & Later

什么是异步?


Any time you wrap a portion of code into a function and specify that it should be executed in response to some event
(timer, mouse click, Ajax response, etc.), you are creating a later chunk of your code, and thus introducing asynchrony to
your program.

将程序分块chunk,只有一个chunk现在执行,其他的chunk稍后执行(chunk通常由function组成)。若使用定时器、ajax请求(默认异步)、鼠标单击监听事件、等待用户输入等,从而指定一部分的代码稍后执行,也就是在程序中引入了异步。

  • 例如我们发送网络请求等待响应,它也可以发送一个同步的ajax请求(最好不要),这时就必须要等到响应回来之后,才可以继续执行剩下的代码。而异步的ajax,他发送请求之后,并不会等待请求回来,而只是注册一下这个事件,就继续执行剩下的代码,将这个事件挂起,通知hosting environment去监听,等待网络请求回来之后,触发这个事件,再把回掉函数放到事件循环队列中。

Event Loop


  1. JS是单线程的

    javascript作为一门浏览器脚本语言,其主要用途在于跟用户交互,以及操作DOM,为了避免带来很复杂的同步问题,所以是单线程工作的。也就是说同一个时间只能做一件事。
    但是JS的单线程并不是说它只有一个线程,它除了一个主线程,还有其他别的很多附加线程,比如有专门用来负责网络请求的线程,有用来进行事件循环的线程。
    JS的单线程,在于它只有一个主线程,就是它的JS引擎,负责执行代码。JS引擎是单线程的,它每次只能执行一段代码,并不会同一时刻执行两段代码,它总是从上到下顺序执行。所以说因为它并没有两个主线程,所以它是单线程的。

  2. Event Loop

    JS的异步(事件循环)主要是由另一个负责事件循环的线程来实现的,JS的异步并不是由JS引擎来实现的,JS引擎没有什么异步不异步的概念,只负责执行代码,不管先执行什么再执行什么,它就是给什么代码就执行什么代码。
    它所执行的代码都是来自于js文件(主线程)和事件循环队列。
    JS引擎依js文件从上到下执行代码,遇到异步的代码,就先注册一个事件,挂起来,等收到相应,就触发这个事件,把回调函数放到event loop中。等到所有的js代码执行完了,主线程空闲了,就会去检查事件循环队列里面有没有异步事件,有就取出来,没有就算了。执行之后主线程又空闲就再一次循环去检查event loop。只要这个回调函数不是死循环,那么这个回调函数就不会一直占用主线程,使得主线程一直繁忙,那么此回调函数执行完了主线程就空闲了,又可以执行下一次循环检查有没有异步事件。

    主线程从”任务队列”中读取事件,这个过程是循环不断的,整个的这种运行机制称为Event Loop(事件循环)。
    每一次循环(tick)都会去任务队列里面取一个任务出来执行。只要主线程空了就回去读取任务队列。
    所以JS的异步也并不都是真正意义上的异步,有内在系统的事件循环机制使得一个异步事件执行完了,主线程空了再读取下一个异步事件

并行与并发


并行是相对串行而言,指同一时刻处理多个进程或线程(物理上同时发生),同一进程的多个线程共享数据空间。而并发是指逻辑上的多个进程任务等一系列连续的操作,不是真是操作系统所说的进程,可能会同时发生,降到物理层(处理器),可能就是串行的。
js只有单线程,所以不需要考虑多线程并行(parallel thread)时遇到的问题。而并发(concurrency)在js中是存在的。

Concurrency is when two or more chains of events interleave over time, such that from a high-level perspective, they appear to be running simultaneously (even though at any given moment only one event is being processed).

以常见的滚动加载为例,这个例子中有两个Process, 滚动发起请求onScroll 和 处理响应结果 onResponse, 这两个是并发的。
“Process” 1 (onscroll events):

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

“Process” 2 (Ajax response events):

1
2
3
4
5
6
7
response 1
response 2
response 3
response 4
response 5
response 6
response 7

如果将这两个Process放入真实的时间线中,是有可能“同时发生”的,这里之所以给同时发生加上引号,是因为根据Event loop的定义,真正意义的同时发生是不可能的,还是按照序列的排队来执行的。

1
2
3
4
5
6
7
8
9
10
11
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

Jobs


是promise的意思,是一种规范。就是说虽然我现在有一系列的任务~我不管它是同步还是异步的任务,反正只有我的第一个任务执行完了,第二个任务才可以执行。
区别:
event loop事件循环,每一次循环都会从事件队列里面取出一个事件,再把它执行。
jobs在每一次事件循环的时候并不会取出一个event,他是要等现在任务队列里的第一个执行完了,再执行第二个,他就是有一种同步的概念。比如说我注册了一系列的异步事件,必须要等第一个执行完了,第二个才开始执行,它是由人为的去控制的,而不是由系统的那个事件循环机制去控制的。

Chapter 2:Callbacks

callback hell回调地狱


回调地狱也称回调金字塔(指函数左边沿的缩进),指的是因为回掉函数的嵌套而造成的 代码结构混乱
其造成的第一个缺点是不匹配我们大脑的思维模式,因为大脑是单线程的,我们在思考问题的时候,会倾向于用同步的思维去理解这些异步的代码,会去判断哪个事件先执行,哪一个稍后执行,而代码结构的混乱,使得我们很难判断回调函数执行的顺序,也使得代码很难维护和更新(大括号太多变得无从下手)。
所以我们要做的就是找到一些解决方案,使得我们可以流畅的进行代码阅读。

Callbacks are the fundamental unit of asynchrony in JS. But they’re not enough for the evolving landscape of async programming as JS matures.
First, our brains plan things out in sequential, blocking, single-threaded semantic ways, but callbacks express Review asynchronous flow in a rather nonlinear, nonsequential way, which makes reasoning properly about such code much harder. Bad to reason about code is bad code that leads to bad bugs.

还有,嵌套的回掉函数,是由其调用者来调用的。

1
2
3
4
5
6
7
8
9
10
function callback1(data){
$.post("url",{data : name},callback2);
}
function callback2(result){
alert(result);
// }callback2在callback1后面调用
function foo(){
$.get("url",callback1);
}
//注意:只有等响应来了,才会把事件放回event队列

callback的信任问题


当我们调用第三方模块的API时,我们无法保证这些模块对回调的处理永远正确的(第三方组件的升级),例如 没有调用回调函数、调用多次、参数出现问题、没有抛出发生的错误 。这就是信任问题,要规避这些问题,就必须写一些代码来防止这些错误的发生。

1. 解决长时间没有调用回掉函数的issue

never being called

  • return之后的返回一个函数,就是回调函数,也不执行,其作用域链中有对前包含函数中那些变量的引用, 然后只有当响应来了的时候,函数才会被放到eventloop中等待被调用。
  • 超过500ms的delay后,setTimeout加入时间循环队列执行,intv被赋值null,定时器主动调用fn(new error())抛出一个超时的错误。
  • 之后要是响应来,执行回调函数,intv是null。

    2. 强制异步执行callback函数–’zalgo’

    因为不确定第三方的API决定的,对回调函数的调用是同步还是异步执行,这样造成我们对程序运行结果的不确定性。
    sync callback invocation

  • 若第三方api给的是同步执行,就先执行return,然后是一个回调函数,因为是同步执行,所以返回的这个函数就会一直等待响应,然后执行代码块2,这个过程是一直占据主线程的,然后0秒之后,setTimeout加到事件循环队列,等待主线程空闲就执行代码块1。
  • 若第三方api给的是异步执行,就先执行return,然后是一个回调函数,先不执行,然后然后0秒之后,执行setTimeout,之后等待响应,来了之后执行代码块3。