歡迎來到旨在探索 JavaScript 以及它的核心元素的系列文章的第四篇。在認識、描述這些核心元素的過程當中,咱們也會分享一些當咱們構建 SessionStack 的時候遵照的一些經驗規則,一個 JavaScript 應用應該保持健壯和高性能來維持競爭力。javascript
若是你錯過了前三章能夠在這兒找到它們:html
此次咱們將展開第一篇文章的內容,回顧一下在單線程環境中編程的缺點,以及如何克服它們來構建出色的 JavaScript UI。按照慣例,在文章的末尾咱們將分享 5 個如何使用 async/await 寫出更簡潔的代碼的技巧。前端
在 第一篇文章 中, 咱們思考了一個問題 當調用棧中的函數調用須要花費咱們很是多的時間,會發生什麼?java
好比,想象一下你的瀏覽器如今正在運行一個複雜的圖像轉換的算法。react
當調用棧有函數在執行,瀏覽器就不能作任何事了 —— 它被阻塞了。這意味着瀏覽器不能渲染頁面,不能運行任何其它的代碼,它就這樣被卡住了。那麼問題來了 —— 你的應用再也不高效和使人滿意了。android
你的應用卡住了。ios
在某些狀況下,這可能不是一個很嚴重的問題。但這實際上是一個更大的問題。一旦你的瀏覽器開始在調用棧運行不少不少的任務,它就頗有可能會長時間得不到響應。在這一點上,大多數的瀏覽器會採起拋出錯誤的解決方案,詢問你是否要終止這個頁面:git
它很醜,而且它會毀了你的用戶體驗:github
你可能會將你的 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.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')
被移出調用棧。cb1
壓入到回調隊列。cb1
,而後把它壓入調用棧。cb1
被執行,而後把 console.log('cb1')
壓入調用棧。console.log('cb1')
被執行。console.log('cb1')
被移出調用棧。cb1
被移出調用棧。快速回顧一下:
有趣的是,ES6 指定了事件循環該如何工做,這意味着在技術上它屬於 JS 引擎的職責範圍了,再也不是宿主環境的一部分了。形成這種變化的一個主要緣由是在 ES6 中引入了 promise,由於後者須要對事件循環隊列的調度操做進行直接的、細微的控制(後面咱們會詳細的討論它們)。
須要重點注意的是 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 中介紹了一種叫 「做業隊列(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();
}
複製代碼
因此,用這樣一種順序的方式來表達你的異步代碼是否是看起來更天然一些了?必定會有方法作到這一點,不是嗎?
看看下面的代碼:
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
如今是否是可用的。
固然,這種基於回調的方式是粗糙的而且有不少不足。這只是初步理解 將來值 以及不須要去擔憂它們何時可用的第一步。
讓咱們看一下這個簡短的例子是如何用 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 設計中最基礎、最重要方面,不該該被忽略。
使用 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() {
// 若是有異常發生,它就會被全局拋出
});
複製代碼
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 內部的原理,知道爲何它爲何如此重要以及去理解你選擇的方法的內部原理。在程序中每種方法都是有利有弊的。
// `rp` 是一個請求異步函數
rp(‘https://api.example.com/endpoint1').then(function(data) {
// …
});
複製代碼
對比:
// `rp` 是一個請求異步函數
var response = await rp(‘https://api.example.com/endpoint1');
複製代碼
對比:
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)
});
複製代碼
async/await
你就能夠逐步的調試 await 調用了,它就像是一個同步函數同樣。編寫 異步 JavaScript 代碼 不只對於應用程序自己而且對於庫也很重要。
好比,SessionStack 記錄 Web 應用、網站中的全部內容:包括全部 DOM 的改變,用戶交互,JavaScript 異常,棧追蹤,網絡請求失敗和 debug 信息。
這一切都發生在你的生產環境中而不會影響你的用戶體驗。咱們須要對咱們的代碼進行大量的優化,使其儘量的異步,這樣咱們就能增長被事件循環處理的事件。
並且這不只是個庫!當你在 SessionStack 要恢復一個用戶的會話時,咱們必須重現全部在用戶的瀏覽器上出現的問題,咱們必須重現整個狀態,容許你在會話的事件軸上來回跳轉。爲了作到這一點,咱們大量地使用了JavaScript 提供的異步操做。
咱們有一個免費的計劃可讓你免費開始。
更多資源:
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。