欢迎来到旨在探索 JavaScript 以及它的核心元素的系列文章的第四篇。在认识、描述这些核心元素的过程当中,咱们也会分享一些当咱们构建 SessionStack 的时候遵照的一些经验规则,一个 JavaScript 应用应该保持健壮和高性能来维持竞争力。javascript
若是你错过了前三章能够在这儿找到它们:html
此次咱们将展开第一篇文章的内容,回顾一下在单线程环境中编程的缺点,以及如何克服它们来构建出色的 JavaScript UI。按照惯例,在文章的末尾咱们将分享 5 个如何使用 async/await 写出更简洁的代码的技巧。前端
为何单线程会限制咱们?
在 第一篇文章 中, 咱们思考了一个问题 当调用栈中的函数调用须要花费咱们很是多的时间,会发生什么?java
好比,想象一下你的浏览器如今正在运行一个复杂的图像转换的算法。react
当调用栈有函数在执行,浏览器就不能作任何事了 —— 它被阻塞了。这意味着浏览器不能渲染页面,不能运行任何其它的代码,它就这样被卡住了。那么问题来了 —— 你的应用再也不高效和使人满意了。android
你的应用卡住了。ios
在某些状况下,这可能不是一个很严重的问题。但这实际上是一个更大的问题。一旦你的浏览器开始在调用栈运行不少不少的任务,它就颇有可能会长时间得不到响应。在这一点上,大多数的浏览器会采起抛出错误的解决方案,询问你是否要终止这个页面:git
它很丑,而且它会毁了你的用户体验:github

JavaScript 程序的单元块
你可能会将你的 JavaScript 代码写在一个 .js 文件中,但你的程序必定是由几个代码块组成的,并且只有一个可以 如今 执行,其他的都会在 以后 执行。最多见的单元块就是函数。web
JavaScript 开发的新手最不能理解的就是 以后 的代码并不必定会在 如今 的代码执行以后执行。换句话说,在定义中不能 如今 马上完成的任务将会异步执行,这意味着可能不会像你认为的那样发生上面所说的阻塞问题。
让咱们来看看下面的例子:
// ajax(..) 是任意库提供的任意一个 Ajax 的函数 var response = ajax('https://example.com/api'); console.log(response); // `response` 不会是响应的 response,由于 Ajax 是异步的 复制代码
你可能已经意识到了,标准的 Ajax 请求不会同步发生,这意味着在代码执行的时候,ajax(..) 函数在没有任何返回值以前,是不会赋值给 response 变量的。
有一个简单的办法去 “等待” 异步函数返回它的结果,就是使用 回调函数:
ajax('https://example.com/api', function(response) { console.log(response); // `response` 如今是有值的 }); 复制代码
注意:虽然其实是能够 同步 实现 Ajax 请求的,可是最好永远都不要这么作。若是你使用了同步的 Ajax 请求,你的 JavaScript 应用就会被阻塞 —— 用户就不能点击、输入数据、导航或是滚动。这将会阻止用户的任何交互动做。这是一种很是糟糕的作法。
这就是使用同步的样子,可是千万不要这么作,不要毁了你的 web 应用:
// 假设你正在使用 jQuery jQuery.ajax({ url: 'https://api.example.com/endpoint', success: function(response) { // 这是你的回调 }, async: false // 这是一个坏主意 }); 复制代码
咱们使用 Ajax 请求只是一个例子。事实上你能够异步执行任何代码。
setTimeout(callback, milliseconds)
也可以异步执行。setTimeout
函数所作的就是设置了一个事件(超时)等待触发执行。咱们来看一看:
function first() { console.log('first'); } function second() { console.log('second'); } function third() { console.log('third'); } first(); setTimeout(second, 1000); // 1000ms 后调用 `second` third(); 复制代码
console 打印出来将会是下面这样的:
first
third
second
复制代码
解析事件循环
咱们先从一个奇怪的说法谈起 —— 尽管 JavaScript 容许异步的代码(就像是咱们刚刚说的 setTimeout
) ,但直到 ES6,JavaScript 自身从未有过任何关于异步的直接概念。JavaScript 引擎只会在任意时刻执行一个程序。
关于 JavaScript 引擎是如何工做的更多细节(特别是 V8 引擎)请看咱们的前一章。
那么,谁会告诉 JS 引擎去执行你的程序?事实上,JS 引擎不是单独运行的 —— 它运行在一个宿主环境中,对于大多数开发者来讲就是典型的浏览器和 Node.js。实际上,现在,JavaScript 被应用到了从机器人到灯泡的各类设备上。每一个设备都表明了一种不一样类型的 JS 引擎的宿主环境。
全部的环境都有一个共同点,就是都拥有一个 事件循环 的内置机制,它随着时间的推移每次都去调用 JS 引擎去处理程序中多个块的执行。
这意味着 JS 引擎只是任意的 JS 代码按需执行的环境。是它周围的环境来调度这些事件(JS 代码执行)。
因此,好比当你的 JavaScript 程序发出了一个 Ajax 请求去服务器获取数据,你在一个函数(回调)中写了 “response” 代码,而后 JS 引擎就会告诉宿主环境: “嘿,我如今要暂停执行了,可是当你完成了这个网络请求,而且获取到数据的时候,请回来调用这个函数。”
而后浏览器设置对网络响应的监听,当它有东西返回给你的时候,它将会把回调函数插入到事件循环队列里而后执行。
咱们来看下面的图:

你能够在前一章了解到更多关于内存堆和调用栈的知识。
那图中的这些 Web API 是什么东西呢?从本质上讲,它们是你没法访问的线程,可是你可以调用它们。它们是浏览器并行启动的一部分。若是你是一个 Node.js 的开发者,这些就是 C++ 的一些 API。
那 事件循环 到底是什么?

事件循环有一个简单的任务 —— 去监控调用栈和回调队列。若是调用栈是空的,它就会取出队列中的第一个事件,而后将它压入到调用栈中,而后运行它。
这样的迭代在事件循环中被称做一个 tick。每个事件就是一个回调函数。
console.log('Hi'); setTimeout(function cb1() { console.log('cb1'); }, 5000); console.log('Bye'); 复制代码
让咱们执行一下这段代码,看看会发生什么:
- 状态是干净的。浏览器 console 是干净的,而且调用栈是空的。

console.log('Hi')
被添加到了调用栈里。

console.log('Hi')
被执行。

console.log('Hi')
被移出调用栈。

setTimeout(function cb1() { ... })
被添加到调用栈。

setTimeout(function cb1() { ... })
执行。浏览器建立了一个定时器(Web API 的一部分),而且开始倒计时。

setTimeout(function cb1() { ... })
自己执行完了,而后被移出调用栈。

console.log('Bye')
被添加到调用栈。

console.log('Bye')
执行。

console.log('Bye')
被移出调用栈。

- 在至少 5000ms 事后,定时器完成,而后将回调
cb1
压入到回调队列。

- 事件循环从回调队列取走
cb1
,而后把它压入调用栈。

cb1
被执行,而后把console.log('cb1')
压入调用栈。

console.log('cb1')
被执行。

console.log('cb1')
被移出调用栈。

cb1
被移出调用栈。

快速回顾一下:

有趣的是,ES6 指定了事件循环该如何工做,这意味着在技术上它属于 JS 引擎的职责范围了,再也不是宿主环境的一部分了。形成这种变化的一个主要缘由是在 ES6 中引入了 promise,由于后者须要对事件循环队列的调度操做进行直接的、细微的控制(后面咱们会详细的讨论它们)。
setTimeout(…) 是如何工做的
须要重点注意的是 setTimeout(…)
不会自动的把你的回调放到事件循环队列中。它设置了一个定时器。当定时器过时了,宿主环境会将你的回调放到事件循环队列中,以便在之后的循环中取走执行它。看看下面的代码:
setTimeout(myCallback, 1000); 复制代码
这并不意味着 myCallback
将会在 1,000ms 以后执行,而是,在 1,000ms 以后将被添加到事件队列。然而,这个队列中可能会拥有一些早一点添加进来的事件 —— 你的回调将会等待被执行。
有不少文章或教程在介绍异步代码的时候都会从 setTimeout(callback, 0) 开始。好了,如今你知道了事件循环作了什么以及 setTimeout 是怎么运行的:以第二个参数是 0 的方式调用 setTimeout 就是推迟到调用栈为空才执行回调。
来看看下面的代码:
console.log('Hi'); setTimeout(function() { console.log('callback'); }, 0); console.log('Bye'); 复制代码
尽管等待的事件设置成 0 了,可是浏览器 console 的结果将会是下面这样:
Hi
Bye
callback
复制代码
ES6 中的做业(Jobs)是什么?
ES6 中介绍了一种叫 “做业队列(Job Queue)” 的新概念。它是事件循环队列之上的一层。你颇有可能会在处理 Promises 的异步的时候遇到它(咱们后面也会讨论到它们)。
咱们如今只简单介绍一下这个概念,以便当咱们讨论 Promises 的异步行为的时候,你能理解这些行为是如何被调度和处理的。
想象一下:做业队列是一个跟在事件队列的每一个 tick 的末尾的一个队列。在事件循环队列的一个 tick 期间可能会发生某些异步操做,这不会致使把一整个新事件添加到事件循环队列中,而是会在当前 tick 的做业队列的末尾添加一项(也就是做业)。
这意味着你能够添加一个稍后执行的功能,而且你能够放心,它会在执行任何其余操做以前执行。
做业还可以使更多的做业被添加到同一个队列的末尾。从理论上说,一个做业的“循环”(一个不停的添加其余做业的做业,等等)可能会无限循环,从而使进入下一个事件循环 tick 的程序的必要资源被消耗殆尽。从概念上讲,这就和你写了一个长时间运行的代码或是死循环(就像是 while (true)
)同样。
做业有点像 setTimeout(callback, 0)
的“hack”,可是它们引入了一个更加明确、更有保证的执行顺序:稍后执行,可是会尽快执行。
回调
众所周知,在 JavaScript 程序中,回调是表达和管理异步目前最经常使用的方式。确实,回调是 JavaScript 中最基础的异步模式。无数的 JS 程序,甚至是很是复杂的 JS 程序,都是使用回调做为异步的基础。
回调也不是没有缺点。许多开发者都尝试去找到更好的异步模式。可是,若是你不理解底层的实际状况,你是不可能有效的去使用任何抽象化的东西。
在下一章中,咱们将深刻挖掘这些抽象的概念来讲明为何更复杂的异步模式(将会在后续的帖子中讨论)是必须的甚至是被推荐的。
嵌套回调
看看下面的代码:
listen('click', function (e){ setTimeout(function(){ ajax('https://api.example.com/endpoint', function (text){ if (text == "hello") { doSomething(); } else if (text == "world") { doSomethingElse(); } }); }, 500); }); 复制代码
咱们有一个三个函数嵌套在一块儿的函数链,每一步都表明异步序列中的一步。
这种代码咱们把它叫作“回调地狱”。可是“回调地狱”显然和嵌套/缩进没有关系。这是个更深层次的问题了。
首先,咱们在等待一个“click”事件,而后等待定时器触发,再而后等着 Ajax 的响应返回,在这点上可能会再次重复。
乍一看,这个代码彷佛能够分解成连续的几个步骤:
listen('click', function (e) { // .. }); 复制代码
而后:
setTimeout(function(){ // .. }, 500); 复制代码
再而后:
ajax('https://api.example.com/endpoint', function (text){ // .. }); 复制代码
最后:
if (text == "hello") { doSomething(); } else if (text == "world") { doSomethingElse(); } 复制代码
因此,用这样一种顺序的方式来表达你的异步代码是否是看起来更天然一些了?必定会有方法作到这一点,不是吗?
Promises
看看下面的代码:
var x = 1; var y = 2; console.log(x + y); 复制代码
这是段简单的代码:它对 x
和 y
求和,而后在控制台打印出来。但,假如 x
或是 y
的值是待肯定的呢?好比说,咱们须要在使用这两个值以前去服务器检索 x
和 y
的值。而后,有两个函数 loadX
和 loadY
,分别从服务器获取 x
和 y
的值。最后,函数 sum
来将获取到的 x
和 y
的值加起来。
看起来就是这样的(至关丑,不是吗?):
function sum(getX, getY, callback) { var x, y; getX(function(result) { x = result; if (y !== undefined) { callback(x + y); } }); getY(function(result) { y = result; if (x !== undefined) { callback(x + y); } }); } // 一个同步或者异步的函数,获取 `x` 的值 function fetchX() { // .. } // 一个同步或者异步的函数,获取 `y` 的值 function fetchY() { // .. } sum(fetchX, fetchY, function(result) { console.log(result); }); 复制代码
这里面的关键点在于 — 这段代码中,x
和 y
是 将来 的值,而后咱们还写了一个 sum(…)
函数,而且从外面看它并不关心 x
或者 y
如今是否是可用的。
固然,这种基于回调的方式是粗糙的而且有不少不足。这只是初步理解 将来值 以及不须要去担忧它们何时可用的第一步。
Promise 值
让咱们看一下这个简短的例子是如何用 Promises 来表达 x + y
的:
function sum(xPromise, yPromise) { // `Promise.all([ .. ])` 接受一个 promises 的数组, // 而且返回一个新的 promise 对象去等待它们 // 所有完成 return Promise.all([xPromise, yPromise]) // 当 promise 完成的时候,咱们就能获取 // `X` and `Y` 的值,而且计算他们 .then(function(values){ // `values` 是一个来自前面完成的 promise // 的消息数组 return values[0] + values[1]; } ); } // `fetchX()` and `fetchY()` 返回 promises 的值,有他们各自的 // 值,或许*如今* 已经准备好了 // 也可能要 *等一下子*。 sum(fetchX(), fetchY()) // 咱们从返回的 promise 获得了这 // 两个数字的和。 // 如今咱们连续的调用了 `then(...)` 去等待已经完成的 // promise。 .then(function(sum){ console.log(sum); }); 复制代码
这段代码能够看到两层 Promises。
fetchX()
和 fetchY()
被直接调用,而后他们的返回值(promises!)被传给 sum(...)
。这些 promises 表明的值可能在 如今 或是 未来 准备好,但每一个 promise 的自身规范都是相同的。咱们以一种与时间无关的方式来解释 x
和 y
的值。它们在一段时间内是 将来值。
第二层 promise 是 sum(...)
建立 (经过 Promise.all([ ... ])
) 并返回的,咱们经过调用 then(...)
来等待返回。当 sum(...)
操做完成的时候,将来值 的总和也就准备就绪了,而后就能够把值打印出来了。咱们隐藏了在 sum(...)
函数内部等待 x
和 y
的 将来值 的逻辑。
注意:在 sum(…)
函数中,Promise.all([ … ])
建立了一个 promise (这个 promise 等待 promiseX
and promiseY
的完成)。链式调用 .then(...)
来建立另外一个 promise,返回的 values[0] + values[1]
会当即执行完成(还要加上加运算的结果)。所以,咱们在 sum(...)
调用结束后加上的 then(...)
— 在上面代码的末尾 — 其实是在第二个 promise 返回后执行,而不是第一个 Promise.all([ ... ])
建立的 promise。还有,尽管咱们没有在第二个 then(...)
后面再进行链式调用,可是它也建立了一个 promise,咱们能够去观察或是使用它。关于 Promise 的链式调用会在后面详细地解释。
使用 Promises,这个 then(...)
的调用其实有两个方法,第一个方法被调用的时机是在已完成的时候 (就像咱们前面使用的那样),而另外一个被调用的时机是已失败的时候:
sum(fetchX(), fetchY())
.then(
// 完成时 function(sum) { console.log( sum ); }, // 失败时 function(err) { console.error( err ); // bummer! } ); 复制代码
若是在获取 x
或者 y
的时候出错了,又或许是在进行加运算的时候失败了,sum(...)
返回的 promise 将会是已失败的状态,而且会将 promise 已失败的值传给 then(...)
的第二个回调处理。
由于 Promises 封装了依赖时间的状态 — 等待内部的值已完成或是已失败 — 从外面看,Promise 是独立于时间的,所以 Promises 能够能经过一种可预测的方式组合起来,而不用去考虑底层的时间或者结果。
并且,一旦 Promise 的状态肯定了,那么他就永远也不会改变状态了 — 在这时它会变成一个 不可改变的值 — 而后就能够在有须要的时候屡次 观察 它。
实际上链式的 promises 是很是有用的:
function delay(time) { return new Promise(function(resolve, reject){ setTimeout(resolve, time); }); } delay(1000) .then(function(){ console.log("after 1000ms"); return delay(2000); }) .then(function(){ console.log("after another 2000ms"); }) .then(function(){ console.log("step 4 (next Job)"); return delay(5000); }) // ... 复制代码
调用 delay(2000)
会建立一个在 2000ms 完成的 promise,而后咱们返回第一个 then(...)
的成功回调,这会致使第二个 then(...)
的 promise 要再等待 2000ms 执行。
注意:由于 Promise 一旦完成了就不能再改变状态了,因此能够安全的传递到任何地方,由于它不会再被意外或是恶意的修改。这对于在多个地方监听 Promise 的解决方案来讲,尤为正确。一方不可能影响到另外一方所监听到的结果。不可变听起来像是一个学术性的话题,可是它是 Promise 设计中最基础、最重要方面,不该该被忽略。
用不用 Promise?
使用 Promises 最重要的一点在于可否肯定一些值是不是真正的 Promise。换句话说,它的值像一个 Promise 吗?
咱们知道 Promises 是由 new Promise(…)
语句构造出来的,你可能会认为 p instanceof Promise
就能判断一个 Promise。其实,并不彻底是。
主要是由于另外一个浏览器窗口(好比 iframe)获取一个 Promise 的值,它拥有本身的 Promise 类,且不一样于当前或其余窗口,因此使用 instance 来区分 Promise 是不许确的。
并且,一个框架或者库能够选择本身的 Promise,而不是使用 ES6 原生的 Promise 实现。事实上,你极可能会在不支持 Promise 的老式浏览器中使用第三方的 Promise 库。
吞噬异常
若是在任何一个建立 Promise 或是对其结果观察的过程当中,抛出了一个 JavaScript 异常错误,好比说 TypeError
或是 ReferenceError
,那么这个异常会被捕获,而后它就会把 Promise 的状态变成已失败。
例如:
var p = new Promise(function(resolve, reject){ foo.bar(); // 对不起,`foo` 没有定义 resolve(374); // 不会执行 :( }); p.then( function fulfilled(){ // 不会执行 :( }, function rejected(err){ // `err` 是 `foo.bar()` 那一行 // 抛出的 `TypeError` 异常对象。 } ); 复制代码
若是一个 Promise 已经结束了,可是在监听结果(在 then(…)
里的回调函数)的时候发生了 JS 异常会怎么样呢?即便这个错误没有丢失,你可能也会对它的处理方式有点惊讶。除非你深刻的挖掘一下:
var p = new Promise( function(resolve,reject){ resolve(374); }); p.then(function fulfilled(message){ foo.bar(); console.log(message); // 不会执行 }, function rejected(err){ // 不会执行 } ); 复制代码
这看起来就像 foo.bar()
的异常真的被吞了。固然了,异常并非被吞了。这是更深层次的问题出现了,咱们没有监听到异常。p.then(…)
调用它本身会返回另外一个 promise,而这个 promise 会由于 TypeError
的异常变为已失败状态。
处理未捕获的异常
还有一些 更好的 办法解决这个问题。
最多见的就是给 Promise 加一个 done(…)
,用来标志 Promise 链的结束。done(…)
不会建立或返回一个 Promise,因此传给 done(..)
的回调显然不会将问题报告给一个不存在的 Promise。
在未捕获异常的状况下,这可能才是你指望的:在 done(..)
已失败的处理函数里的任何异常都会抛出一个全局的未捕获异常(一般是在开发者的控制台)。
var p = Promise.resolve(374); p.then(function fulfilled(msg){ // 数字不会拥有字符串的方法, // 因此会抛出一个错误 console.log(msg.toLowerCase()); }) .done(null, function() { // 若是有异常发生,它就会被全局抛出 }); 复制代码
ES8 发生了什么? Async/await
JavaScript ES8 介绍了 async/await
,使得咱们能更简单的使用 Promises。咱们将简单的介绍 async/await
会带给咱们什么以及如何利用它们写出异步的代码。
因此,来让咱们看看 async/await 是如何工做的。
使用 async
函数声明来定义一个异步函数。这样的函数返回一个 AsyncFunction 对象。AsyncFunction
对象表示执行包含在这个函数中的代码的异步函数。
当一个 async 函数被调用,它返回一个 Promise
。当 async 函数返回一个值,它不是一个 Promise
,Promise
将会被自动建立,而后它使用函数的返回值来决定状态。当 async
抛出一个异常,Promise
使用抛出的值进入已失败状态。
一个 async
函数能够包含一个 await
表达式,它会暂停执行这个函数而后等待传给它的 Promise 完成,而后恢复 async 函数的执行,并返回已成功的值。
你能够把 JavaScript 的 Promise
看做是 Java 的 Future
或是 C#
的 Task。
async/await
的目的是简化使用 promises 的写法。
让咱们来看看下面的例子:
// 一个标准的 JavaScript 函数 function getNumber1() { return Promise.resolve('374'); } // 这个 function 作了和 getNumber1 一样的事 async function getNumber2() { return 374; } 复制代码
一样,抛出异常的函数等于返回已失败的 promises:
function f1() { return Promise.reject('Some error'); } async function f2() { throw 'Some error'; } 复制代码
关键字 await
只能使用在 async
的函数中,并容许你同步等待一个 Promise。若是咱们在 async
函数以外使用 promise,咱们仍然要用 then
回调函数:
async function loadData() { // `rp` 是一个请求异步函数 var promise1 = rp('https://api.example.com/endpoint1'); var promise2 = rp('https://api.example.com/endpoint2'); // 如今,两个请求都被触发, // 咱们就等待它们完成。 var response1 = await promise1; var response2 = await promise2; return response1 + ' ' + response2; } // 但,若是咱们没有在 `async function` 里 // 咱们就必须使用 `then`。 loadData().then(() => console.log('Done')); 复制代码
你还可使用 async 函数表达式的方法建立一个 async 函数。async 函数表达式的写法和 async 函数声明差很少。函数表达式和函数声明最主要的区别就是函数名,它能够在 async 函数表达式中省略来建立一个匿名函数。一个 async 函数表达式能够做为一个 IIFE(当即执行函数) 来使用,当它被定义好的时候就会执行。
它看起来是这样的:
var loadData = async function() { // `rp` 是一个请求异步函数 var promise1 = rp('https://api.example.com/endpoint1'); var promise2 = rp('https://api.example.com/endpoint2'); // 如今,两个请求都被触发, // 咱们就等待它们完成。 var response1 = await promise1; var response2 = await promise2; return response1 + ' ' + response2; } 复制代码
更重要的是,全部主流浏览器都支持 async/await:

若是这个兼容状况不是你想要的,那么也可使用一些 JS 转换器,像 Babel 和 TypeScript。
最后,最重要的是不要盲目的选择“最新”的方法去写异步代码。更重要的是理解异步 JavaScript 内部的原理,知道为何它为何如此重要以及去理解你选择的方法的内部原理。在程序中每种方法都是有利有弊的。
5 个编写可维护的、健壮的异步代码的技巧
- 干净的代码: 使用 async/await 可以让你少写代码。每一次你使用 async/await 你都能跳过一些没必要要的步骤:写一个 .then,建立一个匿名函数来处理响应,在回调中命名响应,好比:
// `rp` 是一个请求异步函数 rp(‘https://api.example.com/endpoint1').then(function(data) { // … }); 复制代码
对比:
// `rp` 是一个请求异步函数 var response = await rp(‘https://api.example.com/endpoint1'); 复制代码
- 错误处理: Async/await 使得咱们可使用相同的代码结构处理同步或者异步的错误 —— 著名的 try/catch 语句。让咱们看看用 Promises 是怎么实现的:
对比:
async function loadData() { try { var data = JSON.parse(await getJSON()); console.log(data); } catch(e) { console.log(e); } } 复制代码
- 条件语句: 使用
async/await
来写条件语句要简单得多:
function loadData() { return getJSON() .then(function(response) { if (response.needsAnotherRequest) { return makeAnotherRequest(response) .then(function(anotherResponse) { console.log(anotherResponse) return anotherResponse }) } else { console.log(response) return response } }) } 复制代码
对比:
async function loadData() { var response = await getJSON(); if (response.needsAnotherRequest) { var anotherResponse = await makeAnotherRequest(response); console.log(anotherResponse) return anotherResponse } else { console.log(response); return response; } } 复制代码
- 栈帧: 和
async/await
不一样的是,根据promise链返回的错误堆栈信息,并不能发现哪出错了。来看看下面的代码:
function loadData() { return callAPromise() .then(callback1) .then(callback2) .then(callback3) .then(() => { throw new Error("boom"); }) } loadData() .catch(function(e) { console.log(err); // Error: boom at callAPromise.then.then.then.then (index.js:8:13) }); 复制代码
对比:
async function loadData() { await callAPromise1() await callAPromise2() await callAPromise3() await callAPromise4() await callAPromise5() throw new Error("boom"); } loadData() .catch(function(e) { console.log(err); // 输出 // Error: boom at loadData (index.js:7:9) }); 复制代码
- 调试: 若是你使用了 promises,你就会知道调试它们将会是一场噩梦。好比,你在 .then 里面打了一个断点,而且使用相似 “stop-over” 这样的 debug 快捷方式,调试器不会移动到下一个 .then,由于它只会对同步代码生效。而经过
async/await
你就能够逐步的调试 await 调用了,它就像是一个同步函数同样。
编写 异步 JavaScript 代码 不只对于应用程序自己而且对于库也很重要。
好比,SessionStack 记录 Web 应用、网站中的全部内容:包括全部 DOM 的改变,用户交互,JavaScript 异常,栈追踪,网络请求失败和 debug 信息。
这一切都发生在你的生产环境中而不会影响你的用户体验。咱们须要对咱们的代码进行大量的优化,使其尽量的异步,这样咱们就能增长被事件循环处理的事件。
并且这不只是个库!当你在 SessionStack 要恢复一个用户的会话时,咱们必须重现全部在用户的浏览器上出现的问题,咱们必须重现整个状态,容许你在会话的事件轴上来回跳转。为了作到这一点,咱们大量地使用了JavaScript 提供的异步操做。
咱们有一个免费的计划可让你免费开始。

更多资源:
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。