diff --git a/event-loop.md b/event-loop.md
index f2def8d..e9c0e7c 100644
--- a/event-loop.md
+++ b/event-loop.md
@@ -10,7 +10,7 @@
在第一篇[文章](https://github.com/Troland/how-javascript-works/blob/master/overview.md)开头,我们考虑了一个问题即当调用栈中含有需要长时间运行的函数调用的时候会发生什么。
-譬如,试想下,在浏览器中运行着一个复杂的图片转化算法。
+譬如,试想下,在浏览器中运行着一个复杂的图片转换算法。
恰好此时调用栈中有函数需要执行,此时浏览器将会被阻塞,它不能够做其它任何事情。这意味着,浏览器会没有响应,不能够进行渲染和运行其它代码。这将会带来问题-程序界面将不再高效和令人愉悦。
@@ -22,11 +22,11 @@

-## JavaScript 程序组件
+## JavaScript 程序的构成模块
你可能会在单一的 .js 文件中书写 JavaScript 程序,但是程序是由多个代码块组成的,当前只有一个代码块在运行,其它代码块将在随后运行。最常见的块状单元是函数。
-大多数 JavaScript 菜鸟有可能需要理解的问题即**之后运行**表示的是并不是必须严格且立即在现在之后执行。换句话说即,根据定义,**现在**不能够运行完毕的任务将会异步完成,这样你就不会不经意间遇到以上提及的 UI 阻塞行为。
+大多数 JavaScript 菜鸟有可能需要理解的问题即**之后运行**表示的是并不是必须严格且立即在现在之后执行。换句话说即,根据定义,**现在**不能够运行完毕的任务将会异步完成,这样你就不会下意识地预期或者希望会遇到以上提及的 UI 阻塞行为。
看下如下代码:
@@ -48,9 +48,9 @@ ajax('https://example.com/api', function(response) {
});
```
-只是要注意一点:即使可以也永远不要发起同步 ajax 请求。如果发起同步 ajax 请求,JavaScript 程序的 UI 将会被阻塞-用户不能够点击,输入数据,跳转或者滚动。这将会冻结任何用户交互体验。这是非常糟糕。
+只是要注意一点:实际上你可以发起同步 ajax 请求。不过,永远不要那么做。如果发起同步 ajax 请求,JavaScript 程序的 UI 将会被阻塞-用户不能够点击,输入数据,跳转或者滚动。这将会冻结任何用户交互体验。这是非常糟糕的实践。
-以下示例代码,但请别这样做,这会毁掉网页:
+以下示例代码,但请别这样做-不要毁掉网页:
```
// 假设你使用 jQuery
@@ -63,7 +63,7 @@ jQuery.ajax({
});
```
-我们以 Ajax 请求为例。你可以异步执行任意代码。
+我们以 Ajax 请求为例。你可以异步执行任意代码块。
你可以使用 `setTimeout(callback, milliseconds)` 函数来异步执行代码。`setTimeout` 函数会在之后的某个时刻触发事件(定时器)。如下代码:
@@ -92,21 +92,23 @@ second
## 事件循环详解
-我们将会以一个有些让人费解的问题开始-尽管允许异步执行 JavaScript 代码(比如之前讨论的 `setTimetout`),但是直到 ES6,实际上 JavaScript 本身并没有集成任何直接的异步编程概念。JavaScript 引擎只允许在任意时刻执行单个的程序片段。
+我们将会以一个有些让人奇怪的声明开始-尽管允许异步 JavaScript 代码(比如之前讨论的 `setTimetout`),但是直到 ES6,实际上 JavaScript 本身并没有集成任何直接的异步编程概念。JavaScript 引擎只允许在任意时刻执行单个的程序片段。
可以查看之前的[文章](https://github.com/Troland/how-javascript-works/blob/master/overview.md)来了解 JavaScript 引擎的工作原理。
-那么, JS 引擎是如何执行程序片段的呢?实际上,JS 引擎并不是隔离运行的-它运行在一个宿主环境中,对大多数开发者来说是典型的 web 浏览器或者 Node.js。实际上,现在 JavaScript 广泛应用于从机器到电灯泡的各种设备之中。每个设备代表了 JS 引擎的不同类型的宿主环境。
+那么, JS 引擎是如何执行程序片段的呢?实际上,JS 引擎并不是隔离运行的-它运行在一个宿主环境中,对大多数开发者来说,一般指的是 web 浏览器或者 Node.js。实际上,现在 JavaScript 广泛嵌入从机器到电灯泡的各种设备之中。每个设备代表了不同类型的 JS 引擎宿主环境。
所有宿主环境都含有一个被称为**事件循环**的内置机制,随着时间的推移,事件循环会执行程序中多个代码片段,每次都会调用 JS 引擎。
-这意味着 JS 引擎只是任意 JS 代码的按需执行环境。这是一个封闭的环境,在其中进行事件的调度(运行JS 代码)。
+这意味着 JS 引擎只是任意 JS 代码的按需执行环境。这是一个周围环境,在其中进行事件的调度(运行JS 代码)。
+
+这里的周围环境根据 ecma 规范,即是说 LexicalEnvironment (词法环境)和VariableEnvironment(变量环境)。
所以,打个比方,当 JavaScript 程序发起 Ajax 请求来从服务器获得数据,你在回调函数中书写 "response" 代码,JS 引擎会告诉宿主环境:
"嘿,我现在要挂起执行了,现在当你完成网络请求的时候且返回了数据,请执行回调函数。"
-之后浏览器会监听从网络中返回的数据,当有数据返回的时候,它会通过把回调函数插入事件循环以便调度执行。
+之后浏览器会监听从网络中返回的数据,当有数据返回的时候,它会通过把回调函数插入事件循环来调度执行。
让我们看下如下图示:
@@ -114,7 +116,7 @@ second
你可以在之前的[文章](https://github.com/Troland/how-javascript-works/blob/master/overview.md)中阅读更多关于动态内存管理和调用栈的信息。
-什么是网页 API ?本质上,你没有权限访问这些线程,你只能够调用它们。它们是浏览器自带的,且可以在浏览器中进行并发操作。如果你是个 Node.js 开发者,这些是 C++ APIs。
+什么是网页 API ?本质上,你没有权限访问这些线程,你只能够调用它们。它们是浏览器的一部分,在其中引入了并发操作。如果你是个 Node.js 开发者,这些是 C++ APIs。
说了那么多,事件循环到底是啥?
@@ -122,7 +124,29 @@ second
事件循环只有一项简单的工作-监测调用栈和回调队列。如果调用栈是空的,它会从回调队列中取得第一个事件然后入栈,并有效地执行该事件。
-事件循环中的这样一次遍历被称为一个 tick。每个事件就是一个回调函数。
+栈的操作是 FIFO 即先进先出,即是说当有两个 setTimeout 执行的时候,那么,第一个会先执行,再执行后一个。
+
+譬如:
+
+```
+setTimeout(function () {
+
+ console.log('first timeout')
+
+}, 0)
+
+setTimeout(function () {
+
+ console.log('second timeout')
+
+}, 0)
+
+// output
+'first timeout'
+'second timeout'
+```
+
+事件循环中的这样一次遍历被称为一个 tick。每个事件就只是一个回调函数。
```
console.log('Hi');
@@ -174,7 +198,9 @@ console.log('Bye');

-11.至少 5 秒之后,定时器结束运行并把 `cb1` 回调添加到回调队列。
+11.至少 5 秒之后,定时器结束运行并把 `cb1` 回调添加到回调队列。为什么说至少五秒呢?
+
+因为 setTimeout 设定的 timer 并不一定会真的在 5 秒后执行,期间需要考虑是否有其它任务在执行,比方说有 microTask 在执行,如 promise 等,根据官方 event loop 文档即可知。

@@ -202,11 +228,11 @@ console.log('Bye');

-令人感兴趣的是,ES6 规定事件循环如何工作的,这意味着从技术上讲,它在 JS 引擎负责的范围之内,而 JS 引擎将不再只是扮演着宿主环境的角色。ES6 中 Promise 的出现是导致改变的主要原因之一,因为 ES6 要求有权限直接细粒度地控制事件循环队列中的调度操作(之后会深入探讨)。
+令人感兴趣的是,ES6 规定事件循环如何工作的,这意味着从技术上讲,它在 JS 引擎负责的范围之内,而不再只是扮演着宿主环境的角色。ES6 中 Promise 的出现是导致改变的主要原因之一,因为 ES6 要求有权直接细粒度地调度操作事件循环队列(之后会深入探讨)。
## setTimeout(…) 工作原理
-需要注意的是 `setTimeout(…)` 并没有自动把回调添加到事件循环队列。它创建了一个定时器。当定时器过期,宿主环境会把回调函数添加至事件循环队列中,然后,在未来的某个 tick 取出并执行该事件。查看如下代码:
+需要注意的是 `setTimeout(…)` 并没有自动把回调添加到事件循环队列。它创建了一个定时器。当定时器过期,宿主环境会把回调函数添加到事件循环队列中,然后,将会在未来的某个 tick 取出并执行该回调。查看如下代码:
```
setTimeout(myCallback, 1000);
@@ -226,7 +252,7 @@ setTimeout(function() {
console.log('Bye');
```
-虽然定时时间设定为 0, 但是控制台中的结果将会如下显示:
+虽然定时时间设定为 0 毫秒, 但是控制台中的结果将会如下显示:
```
Hi
@@ -236,19 +262,19 @@ callback
## ES6 作业概念
-ES6 介绍了一个被称为『作业队列』的概念。它位于事件循环队列的顶部。你极有可能在处理 Promises(之后会介绍) 的异步行为的时候无意间接触到这一概念。
+ES6 介绍了一个被称为『作业队列』的概念。它位于事件循环队列的顶部。你极有可能在处理 Promises(之后会介绍) 的异步行为的时候接触到这一概念。
现在我们将会接触这个概念,以便当讨论 Promises 的异步行为之后,理解如何调度和处理这些行为。
-像这样想象一下:作业队列是附加于事件循环队列中每个 tick 末尾的队列。事件循环的一个 tick 所产生的某些异步操作不会导致添加全新的事件到事件循环队列中,但是反而会在当前 tick 的作业队列末尾添加一个作业项。
+像这样想象一下:作业队列是附加于事件循环队列中每个 tick 末尾的队列。事件循环的一个 tick 中所发生的某些异步操作不会导致将全新的事件添加到事件循环队列中,但是反而会在当前 tick 的作业队列末尾添加一个作业项(即作业)。
-这意味着,你可以添加延时运行其它功能并且你可以确保它会在其它任何功能之前立刻执行。
+这意味着,你可以添加延时运行其它功能且你可以确保它会在其它任何功能之前立刻执行。
一个作业也可以在同一队列末尾添加更多的作业。理论上讲,存在着作业循环的可能性(比如作业不停地添加其它作业)。
-为了无限循环,就会饥饿程序所需要的资源直到下一个事件循环 tick。从概念上讲,这类似于在代码里面书写耗时或者死循环(类似 `while(true)`)。
+为了无限循环,就会饥饿程序所需要的资源直到下一个事件循环 tick。从概念上讲,这类似于在代码里面书写长时间运行或者无限循环(类似 `while(true)`)。
-作业是有些类似于 `setTimeout(callback, 0)` 小技巧,但是是以这样的方式实现的,它们拥有明确定义和有保证的执行顺序:之后且尽快地执行。
+作业是有些类似于 `setTimeout(callback, 0)` 的小技巧,但是是以这样的方式实现的,它们拥有明确定义和有保证的执行顺序:之后而尽快地执行。
## 回调
@@ -256,7 +282,7 @@ ES6 介绍了一个被称为『作业队列』的概念。它位于事件循环
回调并不是没有缺点。许多开发者试图找到更好的异步模式。然而,如果你不理解底层的原理而想要高效地使用任何抽象化的语法这是不可能的。
-在接下来的章节中,我们将会深入探究这些抽象语法并理解更复杂的异步模式的必要性。
+在接下来的章节中,我们将会深入探究这些抽象语法并理解更复杂的异步模式的必要性及是推荐的。
## 嵌套回调
@@ -264,15 +290,14 @@ ES6 介绍了一个被称为『作业队列』的概念。它位于事件循环
```
listen('click', function (e){
- setTimeout(function(){
- ajax('https://api.example.com/endpoint', function (text){
- if (text == "hello") {
- doSomething();
- }
- else if (text == "world") {
- doSomethingElse();
- }
- });
+ setTimeout(function(){
+ ajax('https://api.example.com/endpoint', function (text){
+ if (text == "hello") {
+ doSomething();
+ } else if (text == "world") {
+ doSomethingElse();
+ }
+ });
}, 500);
});
```
@@ -281,7 +306,7 @@ listen('click', function (e){
这类代码通常被称为『回调地狱』。但是,实际上『回调地狱』和代码嵌套及缩进没有任何关系。这是一个更加深刻的问题。
-首先,我们监听点击事件,然后,等待定时器执行,最后等待 Ajax 返回数据,在 Ajax 返回数据的时候,可以重复执行这一过程。
+首先,我们等待点击事件,然后,等待定时器执行,最后等待 Ajax 返回数据,在 Ajax 返回数据的时候,可能会重复执行这一过程。
乍一眼看上去,可以上把以上具有异步特性的代码拆分为按步骤执行的代码,如下所示:
@@ -330,7 +355,7 @@ var y = 2;
console.log(x + y);
```
-这个很直观:计算出 x 和 y 的值然后在控制台打印出来。但是,如果 x 或者 y 的初始值是不存在的且不确定的呢?假设,在表达式中使用 x 和 y 之前,我们需要从服务器得到 x 和 y 的值。想象下,我们拥有函数 `loadX` 和 `loadY` 分别从服务器获取 x 和 y 的值。然后,一旦获得 `x` 和 `y` 的值,就可以使用 `sum` 函数计算出和值。
+这很直观:计算出 x 和 y 的值然后在控制台打印出来。但是,如果 x 或者 y 的初始值是不存在的且不确定的呢?假设,在表达式中使用 x 和 y 之前,我们需要从服务器得到 x 和 y 的值。想象下,我们拥有函数 `loadX` 和 `loadY` 分别从服务器获取 x 和 y 的值。然后,一旦获得 `x` 和 `y` 的值,就可以使用 `sum` 函数计算出和值。
类似如下这样:
@@ -368,7 +393,7 @@ sum(fetchX, fetchY, function(result) {
这里需要记住的一点是-在代码片段中,`x` 和 `y` 是未来值,我们用 `sum(..)`(从外部)来计算和值,但是并没有关注 `x` 和 `y` 是否马上同时有值。
-当然喽,这个粗糙的基于回调的技术还有很多需要改进的地方。这只是理解推出未来值而不用担心何时有返回值的好处的一小步。
+当然喽,这个粗糙的基于回调的技术还有很多需要改进的地方。这只是理解考虑未来值而不用担心何时有返回值的好处的一小步。
## Promise 值
@@ -387,7 +412,7 @@ function sum(xPromise, yPromise) {
} );
}
-// `fetchX()` and `fetchY()` 返回 promise 来取得各自的返回值,这些值返回是无时序的。
+// `fetchX()` and `fetchY()` 返回 promise 来取得各自的返回值,这些值返回顺序不确定。
sum(fetchX(), fetchY())
// 获得一个计算两个数和值的 promise,现在,就可以链式调用 `then(...)` 来处理返回的 promise。
@@ -396,9 +421,9 @@ sum(fetchX(), fetchY())
});
```
-以上代码片段含有两种层次的 Promise。
+以上代码片段含有两层 Promise。
- `fetchX()` 和 `fetchY()` 都是直接调用,它们的返回值(promises!)都被传入 `sum(…)` 作为参数。虽然这些 promises 的 返回值也许会在现在或之后返回,但是无论如何每个 promise 都具有相同的异步行为。我们可以的推算 `x` 和 `y` 是与时间无关的值。暂时称他们为未来值。
+ `fetchX()` 和 `fetchY()` 都是直接调用,它们的返回值(promises!)都被传入 `sum(…)` 作为参数。虽然这些 promises 的 返回值也许会在现在或之后返回,但是无论如何每个 promise 都具有相同的异步行为。我们可以不关心返回时间顺序的方式来考虑 `x` 和 `y` 的值。暂时称他们为未来值。
第二层次的 promise 是由 `sum(…)` (通过 Promise.all([ ... ]))所创建和返回的,然后通过调用 `then(…)` 来等待 promise 的返回值。当 `sum(…)` 运行结束,返回 sum 未来值然后就可以打印出来。我们在 `sum(…)` 内部隐藏了等待未来值 `x` 和 `y` 的逻辑。
@@ -422,7 +447,7 @@ sum(fetchX(), fetchY())
当获取 `x` 或者 `y` 出现错误或者计算和值的时候出现错误,`sum(…)` 返回的 promise 将会失败,传入 `then(…)` 作为第二个参数的回调错误处理程序将会接收 promise 的返回值。
-因为 Promise 封装了时间相关的状态-等待外部的成功或者失败的返回值,Promise 本身是与时间无关的,这样就能够以可预测的方式组成(合并) Promise 而不用关心时序或者返回结果。
+因为 Promise 封装了时间依赖性的状态-等待外部的成功或者失败的返回值,Promise 本身是与时间无关的,这样就能够以可预测的方式组成(合并) Promise 而不用关心定时或者底层结果。
除此之外,一旦 Promise 解析完成,它就会一直保持不可变的状态且可以被随意观察。
@@ -460,13 +485,13 @@ Promise 的一个重要细节即确定某些值是否是真正的 Promise。换
我们知道可以利用 `new Promise(…)` 语法来创建 Promise,然后,你会认为使用 `p instanceof Promise` 来检测某个对象是否是 Promise 类的实例。然而,并不全然如此。
-主要的原因在于你可以从另一个浏览器窗口(比如 iframe)获得 Promise 实例,iframe 中的 Promise 不同于当前浏览器窗口或框架中的 Promise,因此,会导致检测 Promise 实例失败。
+主要的原因在于你可以从另一个浏览器窗口(比如 iframe)获得 Promise 实例,iframe 中的 Promise 不同于当前浏览器窗口或框架中的 Promise,因此,会导致识别 Promise 实例失败。
除此之外,库或框架或许会选择使用自身自带的 Promise 而不是原生的 ES6 实现的 Promise。实际工作中,你可以使用库自带的 Promise 来兼容不支持 Promise 的老版本浏览器。
## 异常捕获
-如果在创建 Promise 或者是在观察解析 Promise 返回结果的任意时刻,遇到了诸如 `TypeError` 或者 `ReferenceError` 的 JavaScript 错误异常,这个异常会被捕获进而强制 Promise 为失败状态。
+如果在创建 Promise 或者是在观察解析 Promise 返回结果的任意时刻,遇到了诸如 `TypeError` 或者 `ReferenceError` 的 JavaScript 错误异常,这个异常会被捕获进而强制进行中的 Promise 为失败状态。
比如:
@@ -532,9 +557,9 @@ function(err) { console.log('err', err);}
有其它许多据说更好的处理异常的技巧。
-普遍的做法是为 Promises 添加 `done(..)` 回调,本质上这会标记 promise 链的状态为 "done."。`done(…)` 并不会创建和返回 promise,因此,当不存在链式 promise 的时候,传入 `done(..)` 的回调显然并不会抛出错误。
+普遍的做法是为 Promises 添加 `done(..)` 回调,本质上这会标记 promise 链的状态为 "done."。`done(…)` 并不会创建和返回 promise,因此,传入 `done(..)` 的回调显然并不会抛出错误到一个不存在的链式 Promise。
-和未捕获的错误状况一样:任何在 `done(..)` 失败处理函数中的异常都将会被抛出为全局错误(基本上是在开发者控制台)。
+和未捕获的错误状况一样:任何在 `done(..)` 失败处理函数中的异常都将会被抛出为全局未捕获错误(基本上是在开发者控制台)。
```
var p = Promise.resolve(374);
@@ -554,11 +579,11 @@ JavaScript ES8 中介绍了 `async/await`,这使得处理 Promises 更加地
那么,让我们瞧瞧 async/await 工作原理。
-使用 `async` 函数定义一个异步函数。该函数会返回[异步函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction)对象。`AsyncFunction` 对象表示在异步函数中运行其内部代码。
+使用 `async` 函数声明定义一个异步函数。该函数会返回[异步函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction)对象。`AsyncFunction` 对象表示在异步函数中运行其内部代码。
当调用异步函数的时候,它会返回一个 `Promise`。异步函数返回值并非 `Promise`,在函数过程中会自动创建一个 `Promise` 并使用函数的返回值来解析该 `Promise`。当 `async` 函数抛出异常,`Promise` 失败回调会获取抛出的异常值。
-`async` 函数可以包含一个 `await` 表达式,这样就可以暂停函数的执行来等待传入的 Promise 的返回结果,之后重启异步函数的执行并返回解析值。
+`async` 函数可以包含一个 `await` 表达式,这样就可以暂停函数的执行来等待传入的 Promise 的解析结果,之后重启异步函数的执行并返回解析值。
你可以把 JavaScript 中的 `Promise` 看作 Java 中的 `Future` 或 `C#` 中的 Task。
@@ -605,7 +630,7 @@ async function loadData() {
loadData().then(() => console.log('Done'));
```
-你也可以使用异步函数表达式来定义异步函数。异步函数表达式拥有和异步函数语句相近的语法。异步函数表达式和异步函数语句的主要区别在于函数名,异步函数表达式可以忽略函数名来创建匿名函数。异步函数表达式可以被用作 IIFE(立即执行函数表达式),可以在定义的时候立即运行。
+你也可以使用异步函数表达式来定义异步函数。异步函数表达式拥有和异步函数语句相近的语法。异步函数表达式和异步函数语句的主要区别在于函数名,异步函数表达式可以忽略函数名来创建匿名函数。异步函数表达式可以被用作 IIFE(立即执行函数表达式),可以在定义的后立即运行。
像这样:
@@ -714,7 +739,7 @@ async function loadData() {
}
```
-4.堆栈桢:和 `async/await` 不同的是,从链式 promise 返回的错误堆栈中无法得知发生错误的地方。看如下代码:
+4.栈桢:和 `async/await` 不同的是,从链式 promise 返回的错误堆栈中无法得知发生错误的地方。看如下代码:
```
function loadData() {
@@ -755,7 +780,7 @@ loadData()
5.调试:如果使用 promise,你就会明白调试它们是一场噩梦。例如,如果你在 .then 代码块中设置一个断点并且使用诸如 "stop-over" 的调试快捷键,调试器不会移动到下一个 .then 代码块,因为调试器只会步进同步代码。
-使用 `async/await` 你可以就像同步代码那样步进到下一个 await 调用。
+使用 `async/await` 你可以就像一般的同步函数那样逐句通过 await 调用。
不仅是程序本身还有库,书写异步 JavaScript 代码都是相当重要的。