摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。怎么處理每個(gè)引擎都有一個(gè)基本組件,稱為調(diào)用棧。也就是說(shuō),如果有其他函數(shù)等待執(zhí)行,函數(shù)是不能離開(kāi)調(diào)用棧的。每個(gè)異步函數(shù)在被送入調(diào)用棧之前必須通過(guò)回調(diào)隊(duì)列。例如方法是在中傳遞的回調(diào)函數(shù)。
?
翻譯:瘋狂的技術(shù)宅
原文:www.valentinog.com/blog/engine…
從Call Stack,Global Memory,Event Loop,Callback Queue到 Promises 和 Async/Await 的 JavaScript引擎之旅!
?
你有沒(méi)有想過(guò)瀏覽器是如何讀取和運(yùn)行 JavaScript 代碼的嗎?這看起來(lái)很神奇,但你可以學(xué)到一些發(fā)生在幕后的事情。讓我們通過(guò)介紹 JavaScript 引擎的精彩世界在這種語(yǔ)言中盡情暢游。
在 Chrome 中打開(kāi)瀏覽器控制臺(tái),然后查看“Sources”標(biāo)簽。你會(huì)看到一個(gè)有趣的命名:Call Stack(在Firefox中,你可以在代碼中插入一個(gè)斷點(diǎn)后看到調(diào)用棧):
?
什么是調(diào)用棧(Call Stack)?看上去像是有很多東西正在運(yùn)行,即使是只執(zhí)行幾行代碼也是如此。實(shí)際上,并不是在所有 Web 瀏覽器上都能對(duì) JavaScript 做到開(kāi)箱即用。
有一個(gè)很大的組件來(lái)編譯和解釋我們的 JavaScript 代碼:它就是 JavaScript 引擎。最受歡迎的 JavaScript 引擎是V8,在 Google Chrome 和 Node.js 中使用,SpiderMonkey 用于 Firefox,以及 Safari/WebKit 所使用的 JavaScriptCore。
今天的 JavaScript 引擎是個(gè)很杰出的工程,盡管它不可能覆蓋瀏覽器工作的方方面面,但是每個(gè)引擎都有一些較小的部件在為我們努力工作。
其中一個(gè)組件是調(diào)用棧,它與全局內(nèi)存和執(zhí)行上下文一起運(yùn)行我們的代碼。你準(zhǔn)備好迎接他們了嗎?
JavaScript 引擎和全局內(nèi)存
我認(rèn)為 JavaScript 既是編譯型語(yǔ)言又是解釋型語(yǔ)言。信不信由你,JavaScript 引擎在執(zhí)行之前實(shí)際上編譯了你的代碼。
是不是聽(tīng)起來(lái)很神奇?這種魔術(shù)被稱為 JIT(即時(shí)編譯)。它本身就是一個(gè)很大的話題,即使是一本書(shū)也不足以描述 JIT 的工作原理。但是現(xiàn)在我們可以跳過(guò)編譯背后的理論,專注于執(zhí)行階段,這仍然是很有趣的。
先看以下代碼:
?
var num = 2; function pow(num) { return num * num; }
如果我問(wèn)你如何在瀏覽器中處理上述代碼?你會(huì)說(shuō)些什么?你可能會(huì)說(shuō)“瀏覽器讀取代碼”或“瀏覽器執(zhí)行代碼”。
現(xiàn)實(shí)中比那更加微妙。首先不是瀏覽器而是引擎讀取該代碼片段。 JavaScript引擎讀取代碼,當(dāng)遇到第一行時(shí),就會(huì)將一些引用放入全局內(nèi)存中。
**全局內(nèi)存(也稱為堆)**是 JavaScript 引擎用來(lái)保存變量和函數(shù)聲明的區(qū)域。所以回到前面的例子,當(dāng)引擎讀取上面的代碼時(shí),全局內(nèi)存中被填充了兩個(gè)綁定:
?
即使例子中只有變量和函數(shù),也要考慮你的 JavaScript 代碼在更大的環(huán)境中運(yùn)行:瀏覽器或在 Node.js 中。在這些環(huán)境中,有許多預(yù)定義的函數(shù)和變量,被稱為全局。全局內(nèi)存將比 num 和 pow 所占用的空間更多。記住這一點(diǎn)。
此時(shí)沒(méi)有執(zhí)行任何操作,但是如果嘗試像這樣運(yùn)行我們的函數(shù)會(huì)怎么樣:
?
var num = 2; function pow(num) { return num * num; } pow(num);
將會(huì)發(fā)生什么?現(xiàn)在事情變得有趣了。當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),JavaScript 引擎會(huì)為另外兩個(gè)盒子騰出空間:
全局執(zhí)行上下文環(huán)境
調(diào)用棧
全局執(zhí)行上下文和調(diào)用棧
在上一節(jié)你了解了 JavaScript 引擎是如何讀取變量和函數(shù)聲明的,他們最終進(jìn)入了全局內(nèi)存(堆)。
但是現(xiàn)在我們執(zhí)行了一個(gè) JavaScript 函數(shù),引擎必須要處理它。怎么處理?每個(gè) JavaScript 引擎都有一個(gè)基本組件,稱為調(diào)用棧。
調(diào)用棧是一個(gè)棧數(shù)據(jù)結(jié)構(gòu):這意味著元素可以從頂部進(jìn)入,但如果在它們上面還有一些元素,就不能離開(kāi)棧。 JavaScript 函數(shù)就是這樣的。
當(dāng)函數(shù)開(kāi)始執(zhí)行時(shí),如果被某些其他函數(shù)卡住,那么它無(wú)法離開(kāi)調(diào)用堆棧。請(qǐng)注意,因?yàn)?strong>這個(gè)概念有助于理解“JavaScript是單線程”這句話。
但是現(xiàn)在讓我們回到上面的例子。 當(dāng)調(diào)用該函數(shù)時(shí),引擎會(huì)將該函數(shù)壓入調(diào)用堆棧中:
?
我喜歡將調(diào)用??醋魇且化B薯片。如果還沒(méi)有先吃掉頂部的所有薯片,就吃不到到底部的薯片!幸運(yùn)的是我們的函數(shù)是同步的:它是一個(gè)簡(jiǎn)單的乘法,可以很快的得到計(jì)算結(jié)果。
同時(shí),引擎還分配了全局執(zhí)行上下文,這是 JavaScript 代碼運(yùn)行的全局環(huán)境。這是它的樣子:
?
想象一下全局執(zhí)行環(huán)境作為一個(gè)海洋,其中 JavaScript 全局函數(shù)就像魚(yú)一樣在里面游泳。多么美好!但這只是故事的一半。如果函數(shù)有一些嵌套變量或一個(gè)或多個(gè)內(nèi)部函數(shù)怎么辦?
即使在下面的簡(jiǎn)單變體中,JavaScript 引擎也會(huì)創(chuàng)建本地執(zhí)行上下文:
?
var num = 2; function pow(num) { var fixed = 89; return num * num; } pow(num);
請(qǐng)注意,我在函數(shù) pow 中添加了一個(gè)名為 fixed 的變量。在這種情況下,本地執(zhí)行上下文中將包含一個(gè)用于保持固定的框。我不太擅長(zhǎng)在小方框里畫(huà)更小的框!你現(xiàn)在必須運(yùn)用自己的想象力。
本地執(zhí)行上下文將出現(xiàn)在 pow 附近,包含在全局執(zhí)行上下文中的綠色框內(nèi)。 你還可以想象,對(duì)于嵌套函數(shù)中的每個(gè)嵌套函數(shù),引擎都會(huì)創(chuàng)建更多的本地執(zhí)行上下文。這些框可以很快的到達(dá)它們?cè)撊サ牡胤健?/p>
單線程的JavaScript
我們說(shuō) JavaScript 是單線程的,因?yàn)橛幸粋€(gè)調(diào)用棧處理我們的函數(shù)。也就是說(shuō),如果有其他函數(shù)等待執(zhí)行,函數(shù)是不能離開(kāi)調(diào)用棧的。
當(dāng)處理同步代碼時(shí),這不是什么問(wèn)題。例如,計(jì)算兩個(gè)數(shù)字的和就是同步的,并且以微秒做為運(yùn)行單位。但是當(dāng)進(jìn)行網(wǎng)絡(luò)通信和與外界的互動(dòng)時(shí)呢?
幸運(yùn)的是 JavaScript引擎被默認(rèn)設(shè)計(jì)為異步。即使他們一次可以執(zhí)行一個(gè)函數(shù),也有一種方法可以讓外部實(shí)體執(zhí)行較慢的函數(shù):在我們的例子中是瀏覽器。我們稍后會(huì)探討這個(gè)話題。
這時(shí),你應(yīng)該了解到當(dāng)瀏覽器加載某些 JavaScript 代碼時(shí),引擎會(huì)逐行讀取并執(zhí)行以下步驟:
使用變量和函數(shù)聲明填充全局內(nèi)存(堆)
將每個(gè)函數(shù)調(diào)用送到調(diào)用棧
創(chuàng)建一個(gè)全局執(zhí)行上下文,其在中執(zhí)行全局函數(shù)
創(chuàng)建了許多微小的本地執(zhí)行上下文(如果有內(nèi)部變量或嵌套函數(shù))
到此為止,你腦子里應(yīng)該有了一個(gè) JavaScript 引擎同步機(jī)制的全景圖。在接下來(lái)的部分中,你將看到異步代碼如何在 JavaScript 中工作以及為什么這樣工作。
異步JavaScript,回調(diào)隊(duì)列和事件循環(huán)
全局內(nèi)存、執(zhí)行上下文和調(diào)用棧解釋了同步 JavaScript 代碼在瀏覽器中的運(yùn)行方式。然而我們還錯(cuò)過(guò)了一些東西。當(dāng)有異步函數(shù)運(yùn)行時(shí)會(huì)發(fā)生什么?
我所指的異步函數(shù)是每次與外界的互動(dòng)都需要一些時(shí)間才能完成的函數(shù)。例如調(diào)用 REST API 或調(diào)用計(jì)時(shí)器是異步的,因?yàn)樗鼈兛赡苄枰獛酌腌姴拍苓\(yùn)行完畢。 現(xiàn)在的 JavaScript 引擎都有辦法處理這種函數(shù)而不會(huì)阻塞調(diào)用堆棧,瀏覽器也是如此。
請(qǐng)記住,調(diào)用堆棧一次只可以執(zhí)行一個(gè)函數(shù),**甚至一個(gè)阻塞函數(shù)都可以直接凍結(jié)瀏覽器。**幸運(yùn)的是,JavaScript 引擎非常智能,并且能在瀏覽器的幫助下解決問(wèn)題。
當(dāng)我們運(yùn)行異步函數(shù)時(shí),瀏覽器會(huì)接受該函數(shù)并運(yùn)行它??紤]下面的計(jì)時(shí)器:
?
setTimeout(callback, 10000); function callback(){ console.log("hello timer!"); }
你肯定多次見(jiàn)到過(guò) setTimeout ,但是你可能不知道它不是一個(gè)內(nèi)置的 JavaScript 函數(shù)。即當(dāng) JavaScript 誕生時(shí),語(yǔ)言中并沒(méi)有內(nèi)置的 setTimeout。
實(shí)際上 setTimeout 是所謂的 Browser API 的一部分,它是瀏覽器提供給我們的便利工具的集合。多么體貼!這在實(shí)踐中意味著什么?由于 setTimeout 是一個(gè)瀏覽器 API,該函數(shù)由瀏覽器直接運(yùn)行(它會(huì)暫時(shí)出現(xiàn)在調(diào)用棧中,但會(huì)立即刪除)。
然后 10 秒后瀏覽器接受我們傳入的回調(diào)函數(shù)并將其移動(dòng)到回調(diào)隊(duì)列。此時(shí)我們的 JavaScript 引擎中還有兩個(gè)框。請(qǐng)看以下代碼:
?
var num = 2; function pow(num) { return num * num; } pow(num); setTimeout(callback, 10000); function callback(){ console.log("hello timer!"); }
可以這樣畫(huà)完成我們的圖:
?
如你所見(jiàn) setTimeout 在瀏覽器上下文中運(yùn)行。 10秒后,計(jì)時(shí)器被觸發(fā),回調(diào)函數(shù)準(zhǔn)備好運(yùn)行。但首先它必須通過(guò)回調(diào)隊(duì)列?;卣{(diào)隊(duì)列是一個(gè)隊(duì)列數(shù)據(jù)結(jié)構(gòu),顧名思義是一個(gè)有序的函數(shù)隊(duì)列。
每個(gè)**異步函數(shù)在被送入調(diào)用棧之前必須通過(guò)回調(diào)隊(duì)列。**但誰(shuí)推動(dòng)了這個(gè)函數(shù)呢?還有另一個(gè)名為 Event Loop 的組件。
Event Loop 現(xiàn)在只做一件事:它應(yīng)檢查調(diào)用棧是否為空。如果回調(diào)隊(duì)列中有一些函數(shù),并且如果調(diào)用棧是空閑的,那么這時(shí)應(yīng)將回調(diào)送到調(diào)用棧。在完成后執(zhí)行該函數(shù)。
這是用于處理異步和同步代碼的 JavaScript 引擎的大圖:
?
想象一下,callback() 已準(zhǔn)備好執(zhí)行。當(dāng) pow() 完成時(shí),**調(diào)用棧為空,事件循環(huán)推送 **callback()。就是這樣!即使我簡(jiǎn)化了一些東西,如果你理解了上面的圖,那么就可以理解 JavaScript 的一切了。
請(qǐng)記?。?strong>Browser API、回調(diào)隊(duì)列和事件循環(huán)是異步 JavaScript 的支柱。
如果你喜歡視頻,我建議去看 Philip Roberts 的視頻:事件循環(huán)是什么。這是關(guān)于時(shí)間循環(huán)的最好的解釋之一。
堅(jiān)持下去,因?yàn)槲覀冞€沒(méi)有使用異步 JavaScript。在后面的內(nèi)容中,我們將詳細(xì)介紹 ES6 Promises。
回調(diào)地獄和 ES6 的 Promise
JavaScript 中的回調(diào)函數(shù)無(wú)處不在。它們用于同步和異步代碼。例如 map 方法:
?
function mapper(element){ return element * 2; } [1, 2, 3, 4, 5].map(mapper);
mapper 是在 map 中傳遞的回調(diào)函數(shù)。上面的代碼是同步的。但要考慮一個(gè)間隔:
?
function runMeEvery(){ console.log("Ran!"); } setInterval(runMeEvery, 5000);
該代碼是異步的,我們?cè)?setInterval 中傳遞了回調(diào) runMeEvery?;卣{(diào)在 JavaScript 中很普遍,所以近幾年里出現(xiàn)了一個(gè)問(wèn)題:回調(diào)地獄。
JavaScript中的回調(diào)地獄指的是編程的“風(fēng)格”,回調(diào)嵌套在嵌套在…...其他回調(diào)中的回調(diào)中。正是由于 JavaScript 的異步性質(zhì)導(dǎo)致程序員掉進(jìn)了這個(gè)陷阱。
說(shuō)實(shí)話,我從來(lái)沒(méi)有碰到過(guò)極端的回調(diào)金字塔,也許是因?yàn)槲抑匾暣a的可讀性,并且總是試著堅(jiān)持這個(gè)原則。如果你發(fā)現(xiàn)自己掉進(jìn)了回調(diào)地獄,那就說(shuō)明你的函數(shù)太多了。
我不會(huì)在這里討論回調(diào)地獄,如果你很感興趣的話,給你推薦一個(gè)網(wǎng)站: callbackhell.com 更深入地探討了這個(gè)問(wèn)題并提供了一些解決方案。我們現(xiàn)在要關(guān)注的是 ES6 Promise。 ES6 Promise 是對(duì) JavaScript 語(yǔ)言的補(bǔ)充,旨在解決可怕的回調(diào)地獄。但 Promise 是什么?
JavaScript Promise 是未來(lái)事件的表示。Promise 能夠以 success 結(jié)束:用行話說(shuō)就是它已經(jīng) resolved(已經(jīng)完成)。但如果 Promise 出錯(cuò),我們會(huì)說(shuō)它處于rejected狀態(tài)。 Promise 也有一個(gè)默認(rèn)狀態(tài):每個(gè)新Promise都以 pending 狀態(tài)開(kāi)始。
創(chuàng)建和使用 Promise
要?jiǎng)?chuàng)建新的 Promise,可以通過(guò)將回調(diào)函數(shù)傳給要調(diào)用的 Promise 構(gòu)造函數(shù)的方法?;卣{(diào)函數(shù)可以使用兩個(gè)參數(shù):resolve 和 reject。讓我們創(chuàng)建一個(gè)新的 Promise,它將在5秒后 resolve(你可以在瀏覽器的控制臺(tái)中嘗試這些例子):
?
const myPromise = new Promise(function(resolve){ setTimeout(function(){ resolve() }, 5000) });
如你所見(jiàn),resolve 是一個(gè)函數(shù),我們調(diào)用它使 Promise 成功。下面的例子中 reject 將得到 rejected 的 Promise:
?
const myPromise = new Promise(function(resolve, reject){ setTimeout(function(){ reject() }, 5000) });
請(qǐng)注意,在第一個(gè)示例中,你可以省略 reject ,因?yàn)樗堑诙€(gè)參數(shù)。但是如果你打算使用 reject**,就不能省略 resolve**。換句話說(shuō),以下代碼將無(wú)法工作,最終將以 resolved 的 Promise 結(jié)束:
?
// Can"t omit resolve ! const myPromise = new Promise(function(reject){ setTimeout(function(){ reject() }, 5000) });
現(xiàn)在 Promise 看起來(lái)不是那么有用。這些例子不向用戶打印任何內(nèi)容。讓我們添加一些數(shù)據(jù)。 resolved 的和rejected 的 Promises 都可以返回?cái)?shù)據(jù)。這是一個(gè)例子:
?
const myPromise = new Promise(function(resolve) { resolve([{ name: "Chris" }]); });
但我們?nèi)匀豢床坏饺魏螖?shù)據(jù)。 要從 Promise 中提取數(shù)據(jù),你還需要一個(gè)名為 then 的方法。它需要一個(gè)回調(diào)(真是具有諷刺意味?。﹣?lái)接收實(shí)際的數(shù)據(jù):
?
const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); myPromise.then(function(data) { console.log(data); });
作為 JavaScript 開(kāi)發(fā)人員,你將主要與來(lái)自外部的 Promises 進(jìn)行交互。相反,庫(kù)的開(kāi)發(fā)者更有可能將遺留代碼包裝在 Promise 構(gòu)造函數(shù)中,如下所示:
?
const shinyNewUtil = new Promise(function(resolve, reject) { // do stuff and resolve // or reject });
在需要時(shí),我們還可以通過(guò)調(diào)用 Promise.resolve() 來(lái)創(chuàng)建和解決 Promise:
?
Promise.resolve({ msg: "Resolve!"}) .then(msg => console.log(msg));
所以回顧一下,JavaScript Promise 是未來(lái)發(fā)生的事件的書(shū)簽。事件以掛起狀態(tài)開(kāi)始,可以成功(resolved,fulfilled)或失敗(rejected)。 Promise 可以返回?cái)?shù)據(jù),通過(guò)把 then 附加到 Promise 來(lái)提取數(shù)據(jù)。在下一節(jié)中,我們將看到如何處理來(lái)自 Promise 的錯(cuò)誤。
ES6 Promise 中的錯(cuò)誤處理
JavaScript 中的錯(cuò)誤處理一直很簡(jiǎn)單,至少對(duì)于同步代碼而言。請(qǐng)看下面的例子:
?
function makeAnError() { throw Error("Sorry mate!"); } try { makeAnError(); } catch (error) { console.log("Catching the error! " + error); }
輸出將是:
?
Catching the error! Error: Sorry mate!
錯(cuò)誤在 catch 塊中被捕獲?,F(xiàn)在讓我們嘗試使用異步函數(shù):
?
function makeAnError() { throw Error("Sorry mate!"); } try { setTimeout(makeAnError, 5000); } catch (error) { console.log("Catching the error! " + error); }
由于 setTimeout,上面的代碼是異步的。如果運(yùn)行它會(huì)發(fā)生什么?
?
throw Error("Sorry mate!"); ^ Error: Sorry mate! at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9)
這次輸出是不同的。錯(cuò)誤沒(méi)有通過(guò) catch塊。它可以自由地在棧中傳播。
那是因?yàn)?try/catch 僅適用于同步代碼。如果你感到好奇,可以在 Node.js 中的錯(cuò)誤處理中得到該問(wèn)題的詳細(xì)解釋。
幸運(yùn)的是,Promise 有一種處理異步錯(cuò)誤的方法,就像它們是同步的一樣。
?
const myPromise = new Promise(function(resolve, reject) { reject("Errored, sorry!"); });
在上面的例子中,我們可以用 catch 處理程序錯(cuò)誤,再次采取回調(diào):
?
const myPromise = new Promise(function(resolve, reject) { reject("Errored, sorry!"); }); myPromise.catch(err => console.log(err));
我們也可以調(diào)用 Promise.reject() 來(lái)創(chuàng)建和 reject Promise:
?
Promise.reject({msg: "Rejected!"}).catch(err => console.log(err));
ES6 Promise 組合器:Promise.all,Promise.allSettled,Promise.any和它們的小伙伴
Promise 并不是在孤軍奮戰(zhàn)。 Promise API 提供了一系列將 Promise 組合在一起的方法。其中最有用的是Promise.all,它接受一系列 Promise 并返回一個(gè)Promise。問(wèn)題是當(dāng)任何一個(gè)Promise rejected時(shí),Promise.all 就會(huì) rejects 。
Promise.race 在數(shù)組中的一個(gè) Promise 結(jié)束后立即 resolves 或 reject。如果其中一個(gè)Promise rejects ,它仍然會(huì)rejects。
較新版本的 V8 也將實(shí)現(xiàn)兩個(gè)新的組合器:**Promise.allSettled **和 Promise.any。 Promise.any 仍處于提案的早期階段:在撰寫(xiě)本文時(shí),還不支持。
Promise.any 可以表明任何 Promise 是否 fullfilled。與 Promise.race 的區(qū)別在于 Promise.any 不會(huì) reject,即使是其中一個(gè)Promise 被 rejected。
最有趣的是 Promise.allSettled。它仍然需要一系列的 Promise,但如果其中一個(gè) Promise rejects 的話 ,它不會(huì)被短路。當(dāng)你想要檢查 Promise 數(shù)組中是否全部已解決時(shí),它是有用的。可以認(rèn)為它總是和 Promise.all 對(duì)著干。
ES6 Promise 和 microtask 隊(duì)列
如果你還記得前面的章節(jié)**,JavaScript 中的每個(gè)異步回調(diào)函數(shù)都會(huì)在被推入調(diào)用棧之前在回調(diào)隊(duì)列中結(jié)束**。但是在 Promise 中傳遞的回調(diào)函數(shù)有不同的命運(yùn):它們由微任務(wù)隊(duì)列處理,而不是由回調(diào)隊(duì)列處理。
你應(yīng)該注意一個(gè)有趣的現(xiàn)象:微任務(wù)隊(duì)列優(yōu)先于回調(diào)隊(duì)列。當(dāng)事件循環(huán)檢查是否有任何新的回調(diào)準(zhǔn)備好被推入調(diào)用棧時(shí),來(lái)自微任務(wù)隊(duì)列的回調(diào)具有優(yōu)先權(quán)。
Jake Archibald 在任務(wù)、微任務(wù)、隊(duì)列和時(shí)間表一文中更詳細(xì)地介紹了這些機(jī)制,這是一篇很棒的文章。
異步的進(jìn)化:從 Promise 到 async/await
JavaScript 正在快速發(fā)展,每年我們都會(huì)不斷改進(jìn)語(yǔ)言。Promise 似乎是到達(dá)了終點(diǎn),但 **ECMAScript 2017(ES8)的新語(yǔ)法誕生了:async / await **。
async/await 只是一種風(fēng)格上的改進(jìn),我們稱之為語(yǔ)法糖。 async/await 不會(huì)以任何方式改變 JavaScript(請(qǐng)記住,JavaScript 必須向后兼容舊瀏覽器,不應(yīng)破壞現(xiàn)有代碼)。
它只是一種基于 Promise 編寫(xiě)異步代碼的新方法。讓我們舉個(gè)例子。之前我們用 then 的 Promise:
?
const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); myPromise.then((data) => console.log(data))
現(xiàn)在使用async/await,我們可以從另一個(gè)角度看待用同步的方式處理異步代碼。我們可以將 Promise 包裝在標(biāo)記為 async 的函數(shù)中,然后等待結(jié)果:
?
const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); async function getData() { const data = await myPromise; console.log(data); } getData();
現(xiàn)在有趣的是異步函數(shù)將始終返回 Promise,并且沒(méi)人能阻止你這樣做:
?
async function getData() { const data = await myPromise; return data; } getData().then(data => console.log(data));
怎么處理錯(cuò)誤呢? async/await 提供的一個(gè)好處就是有機(jī)會(huì)使用 try/catch。 (參見(jiàn)異步函數(shù)中的異常處理及測(cè)試方法 )。讓我們?cè)倏匆幌翽romise,我們使用catch處理程序來(lái)處理錯(cuò)誤:
?
const myPromise = new Promise(function(resolve, reject) { reject("Errored, sorry!"); }); myPromise.catch(err => console.log(err));
使用異步函數(shù),我們可以重構(gòu)以下代碼:
?
async function getData() { try { const data = await myPromise; console.log(data); // or return the data with return data } catch (error) { console.log(error); } } getData();
不是每個(gè)人都會(huì)用這種風(fēng)格。 try/catch 會(huì)使你的代碼混亂。雖然用 try/catch還有另一個(gè)問(wèn)題要指出。請(qǐng)看以下代碼,在try塊中引發(fā)錯(cuò)誤:
?
async function getData() { try { if (true) { throw Error("Catch me if you can"); } } catch (err) { console.log(err.message); } } getData() .then(() => console.log("I will run no matter what!")) .catch(() => console.log("Catching err"));
哪一字符串會(huì)打印到控制臺(tái)?請(qǐng)記住,try/catch是一個(gè)同步構(gòu)造,但我們的異步函數(shù)會(huì)產(chǎn)生一個(gè) Promise。他們?cè)趦蓷l不同的軌道上行駛,就像兩列火車。但他們永遠(yuǎn)不會(huì)碰面!也就是說(shuō),throw 引發(fā)的錯(cuò)誤永遠(yuǎn)不會(huì)觸發(fā) getData() 的 catch 處理程序。運(yùn)行上面的代碼將導(dǎo)致 “抓住我,如果你可以”,然后“不管怎樣我都會(huì)跑!”。
實(shí)際上我們不希望 throw 觸發(fā)當(dāng)前的處理。一種可能的解決方案是從函數(shù)返回 Promise.reject():
?
async function getData() { try { if (true) { return Promise.reject("Catch me if you can"); } } catch (err) { console.log(err.message); } }
現(xiàn)在錯(cuò)誤將按預(yù)期處理:
?
getData() .then(() => console.log("I will NOT run no matter what!")) .catch(() => console.log("Catching err")); "Catching err" // output
除此之外 async/await 似乎是在 JavaScript 中構(gòu)建異步代碼的最佳方式。我們可以更好地控制錯(cuò)誤處理,代碼看起來(lái)更清晰。
我不建議把所有的 JavaScript 代碼都重構(gòu)為 async/await。這必須是與團(tuán)隊(duì)討論之后的選擇。但是如果你自己工作的話,無(wú)論你使用簡(jiǎn)單的 Promise 還是 async/await 都是屬于個(gè)人偏好的問(wèn)題。
總結(jié)
JavaScript 是一種用于Web的腳本語(yǔ)言,具有先被編譯然后再由引擎解釋的特性。在最流行的 JavaScript 引擎中,有 Google Chrome 和 Node.js 使用的V8,為網(wǎng)絡(luò)瀏覽器 Firefox 構(gòu)建的 SpiderMonkey,由Safari使用的 JavaScriptCore。
JavaScript 引擎有很多部分組成:調(diào)用棧、全局內(nèi)存、事件循環(huán)和回調(diào)隊(duì)列。所有這些部分在完美的調(diào)整中協(xié)同工作,以便在 JavaScript 中處理同步和異步代碼。
JavaScript 引擎是單線程的,這意味著只有一個(gè)用于運(yùn)行函數(shù)的調(diào)用堆棧。這種限制是 JavaScript 異步性質(zhì)的基礎(chǔ):所有需要時(shí)間的操作必須由外部實(shí)體(例如瀏覽器)或回調(diào)函數(shù)負(fù)責(zé)。
為了簡(jiǎn)化異步代碼流程,ECMAScript 2015 給我們帶來(lái)了 Promises。 Promise 是一個(gè)異步對(duì)象,用于表示異步操作的失敗或成功。但改進(jìn)并沒(méi)有止步于此。 2017年 async/await誕生了:它是 Promise 的一種風(fēng)格上的彌補(bǔ),可以用來(lái)編寫(xiě)異步代碼,就好像它是同步的一樣。
歡迎關(guān)注前端公眾號(hào):前端先鋒,獲取前端工程化實(shí)用工具包。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/6686.html
摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。單線程的我們說(shuō)是單線程的,因?yàn)橛幸粋€(gè)調(diào)用棧處理我們的函數(shù)。也就是說(shuō),如果有其他函數(shù)等待執(zhí)行,函數(shù)是不能離開(kāi)調(diào)用棧的。每個(gè)異步函數(shù)在被送入調(diào)用棧之前必須通過(guò)回調(diào)隊(duì)列。 翻譯:瘋狂的技術(shù)宅原文:https://www.valentinog.com/bl... 本文首發(fā)微信公眾號(hào):前端先鋒歡迎關(guān)注,每天都給你推送新鮮的前端技術(shù)文章 sh...
摘要:最受歡迎的引擎是,由和使用,用于,以及使用的。引擎它們是如何工作的全局執(zhí)行上下文和調(diào)用堆棧剛剛了解了引擎如何讀取變量和函數(shù)聲明,它們最終被放入了全局內(nèi)存堆中。事件循環(huán)只有一個(gè)任務(wù)它檢查調(diào)用堆棧是否為空。 為了保證可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 有沒(méi)有想過(guò)瀏覽器如何讀取和運(yùn)行JS代碼? 這看起來(lái)很神奇,我們可以通過(guò)瀏覽...
摘要:事件循環(huán)從回調(diào)隊(duì)列中獲取并將其推入調(diào)用堆棧。執(zhí)行從調(diào)用堆棧中移除從調(diào)用堆棧中移除快速回顧值得注意的是,指定了事件循環(huán)應(yīng)該如何工作,這意味著在技術(shù)上它屬于引擎的職責(zé)范圍,不再僅僅扮演宿主環(huán)境的角色。 此篇是 JavaScript是如何工作的第四篇,其它三篇可以看這里: JavaScript是如何工作的:引擎,運(yùn)行時(shí)和調(diào)用堆棧的概述! JavaScript是如何工作的:深入V8引擎&編寫(xiě)...
摘要:事件循環(huán)從回調(diào)隊(duì)列中獲取并將其推送到調(diào)用堆棧。如何工作請(qǐng)注意,不會(huì)自動(dòng)將您的回調(diào)函數(shù)放到事件循環(huán)隊(duì)列中。它設(shè)置了一個(gè)計(jì)時(shí)器,當(dāng)計(jì)時(shí)器到期時(shí),環(huán)境將您的回調(diào)函數(shù)放入事件循環(huán)中,以便將來(lái)的某個(gè)事件會(huì)將其選中并執(zhí)行它。 我們將通過(guò)回顧第一篇文章中單線程編程的缺點(diǎn),然后在討論如何克服它們來(lái)構(gòu)建令人驚嘆的JavaScript UI。在文章結(jié)尾處,我們將分享5個(gè)關(guān)于如何使用async / awai...
showImg(https://segmentfault.com/img/bVbjYU7?w=2000&h=1333); 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! JavsScript 是一門單線程的編程語(yǔ)言,這就意味著一個(gè)時(shí)間里只能處理一件事,也就是說(shuō) JavaScript 引擎一次只能在一個(gè)線程里處理一條語(yǔ)句。 雖然單線程簡(jiǎn)化了編程代碼,因?yàn)槟悴槐靥珦?dān)心并發(fā)引出的問(wèn)...
閱讀 2047·2021-11-22 09:34
閱讀 3381·2021-09-28 09:35
閱讀 13922·2021-09-09 11:34
閱讀 3696·2019-08-29 16:25
閱讀 2899·2019-08-29 15:23
閱讀 2103·2019-08-28 17:55
閱讀 2500·2019-08-26 17:04
閱讀 3101·2019-08-26 12:21