摘要:在中,表示抽象的非阻塞異步執行。在完成之后安排代碼的唯一方式是通過方法綁定回調函數。下圖描述了該示例的計算過程方法中綁定的回調函數只有當成功的時候才會調用。為了處理失敗的,需要通過綁定另一個回調函數。
介紹
ES7中,async/await 語法使異步promise的協調變得很簡單。如果你需要以特定順序異步獲取來自多個數據庫或API的數據,可以使用雜亂的promise或回調函數。async/await使我們可以更簡便地處理這種邏輯,代碼的可讀性和可維護性也更好。
在該教程中,我們用圖表和一些簡單的例子來解釋async/await的語法和語義。
開始講解之前,我們先對promise進行一個簡單的概述,如果你對promise已經很熟悉了,可以跳過該部分內容。
在js中,promise表示抽象的非阻塞異步執行。js中的promise與Java中的 Future或C#中的Task很相似。
promise通常用于網絡和I/O操作-例如,讀取文件,發起HTTP請求。為了不阻塞當前執行線程,我們創建一個異步promise,使用then方法綁定一個回調函數,該回調函數會在promise完成后觸發。回調函數本身也可以返回一個promise,所以promise可以高效的鏈式調用。
簡單起見,所有的例子中我們都假定request-promise庫已經安裝和加載完成了,如下所示:
var rp = require("request-promise");
現在我們可以像這樣發起一個簡單的HTTP GET請求,該方法返回一個promise:
const promise = rp("http://example.com/")
接下來,看一個例子:
console.log("Starting Execution"); const promise = rp("http://example.com/"); promise.then(result => console.log(result)); console.log("Can"t know if promise has finished yet...");
在第三行,我們創建了一個promise,然后我們在第四行中為其綁定了一個回調函數。由于promise是異步執行的
,所以執行到第六行時,我們不確定promise有沒有完成。多次運行上面的代碼,得到的結果可能每次都不一樣。更通俗地講,promise后面的代碼和promise是并行運行的。
在promise完成之前,沒有辦法中斷當前的操作序列。這與Java中的 Future.get是不同的,Future.get允許我們中斷當前的線程直到Future完成。js中,我們不會輕易地等待promise執行完成。在promise完成之后安排代碼的唯一方式是通過then方法綁定回調函數。
下圖描述了該示例的計算過程:
then方法中綁定的回調函數只有當promise成功的時候才會調用。如果promise失敗的話(例如,由于網絡錯誤),回調不會執行。為了處理失敗的promise,需要通過catch綁定另一個回調函數。
rp("http://example.com/"). then(() => console.log("Success")). catch(e => console.log(`Failed: ${e}`))
最后,為了測試一下效果,我們通過Promise.resolve和Promise.reject簡單地生成成功和失敗的promise:
const success = Promise.resolve("Resolved"); // Will print "Successful result: Resolved" success. then(result => console.log(`Successful result: ${result}`)). catch(e => console.log(`Failed with: ${e}`)) const fail = Promise.reject("Err"); // Will print "Failed with: Err" fail. then(result => console.log(`Successful result: ${result}`)). catch(e => console.log(`Failed with: ${e}`))
有關promise更詳細的教程,查看這篇文章
問題-組合Promise單個promise是很簡單的。可是,我們編寫復雜的異步邏輯時,可能需要組合使用多個promise來處理。大量的then語句和匿名回調函數很容易讓代碼變得不可維護。
例如,我們要編寫一個如下功能的代碼:
發起一個HTTP請求,等待完成后,打印出結果
然后發起兩個并行的HTTP請求;
后兩個請求都完成后,打印出他們的結果。
下面的代碼片段演示了上述功能的實現:
// Make the first call const call1Promise = rp("http://example.com/"); call1Promise.then(result1 => { // Executes after the first request has finished console.log(result1); const call2Promise = rp("http://example.com/"); const call3Promise = rp("http://example.com/"); return Promise.all([call2Promise, call3Promise]); }).then(arr => { // Executes after both promises have finished console.log(arr[0]); console.log(arr[1]); })
首先發起第一個HTTP請求,當該請求完成后,調用它的回調函數(1-3行)。在回調函數中,我們又相繼發起兩個HTTP請求生成了兩個promise。這兩個promise并行運行;當他們都執行完后,我們還需要為其綁定一個回調函數。因此,我們用promise.all將這兩個promise組合成一個promise, 只有當他們都完成后,這個promise才會完成。由于第一個回調函數的結果是promise,因此我們鏈式地調用另一個then方法和回調函數輸出最終結果。
下圖描述了這個執行過程:
對于這么簡單的例子,我們就用了兩個then回調和promise.all來同步并行的promise。試想如果我們執行更多的異步操作或者增加錯誤處理函數呢?這種方式很容易讓代碼變成一堆雜亂的then、promise.all和回調函數。
Async 函數async 函數提供了一種簡潔的方式來定義一個返回promise的函數。
例如,下面兩種定義是等價的:
function f() { return Promise.resolve("TEST"); } // asyncF is equivalent to f! async function asyncF() { return "TEST"; }
相似地,在異步函數拋出異常與返回一個reject promise對象的函數等價:
function f() { return Promise.reject("Error"); } // asyncF is equivalent to f! async function asyncF() { throw "Error"; }Await
我們不能同步等待promise的完成。只能通過then方法傳入一個回調函數。我們鼓勵非阻塞編程,因此同步等待promise是不允許的。否則,開發者會產生編寫同步腳本的想法,畢竟同步編程要簡單的多。
但是,為了同步promise我們需要允許他們等待彼此的完成。換句話說,如果操作是異步的(也就是說包裹在promise中),它應該可以等待其他異步操作的完成。但是,js解析器怎么知道操作是否跑在promise中?
答案是async關鍵字。每個async函數返回一個promise。因此,js解析器知道所有的操作都位于async函數中,并將所有的代碼包裹在promise中異步地執行。所以,async函數,允許操作等待其他promise的完成。
說一下await關鍵字。它只能用在async函數中,允許我們同步等待promise的完成。如果在async函數外邊使用promise,我們仍然需要使用then回調函數。
async function f(){ // response will evaluate as the resolved value of the promise const response = await rp("http://example.com/"); console.log(response); } // We can"t use await outside of async function. // We need to use then callbacks .... f().then(() => console.log("Finished"));
現在我們看一下前面的那個例子如何用async/await進行改寫:
/ Encapsulate the solution in an async function async function solution() { // Wait for the first HTTP call and print the result console.log(await rp("http://example.com/")); // Spawn the HTTP calls without waiting for them - run them concurrently const call2Promise = rp("http://example.com/"); // Does not wait! const call3Promise = rp("http://example.com/"); // Does not wait! // After they are both spawn - wait for both of them const response2 = await call2Promise; const response3 = await call3Promise; console.log(response2); console.log(response3); } // Call the async function solution().then(() => console.log("Finished"));
以上代碼,我們的解決方案就封裝在了async函數中。我們可以直接await promise的執行,省掉了then回調函數。最后,我們只需要調用async函數。它封裝了調用其他promise的邏輯,并返回一個promise。
實際上在上面的例子中,promise是并行觸發的。本例中也一樣(7-8行)。注意第12-13行我們使用了await阻塞主線程,等待所有的promise執行完成。后面,我們看到promise都完成了,和前面的例子類似(promise.all(...).then(...))。
其執行流程與前例的流程是相等的。但是,代碼變得更具可讀性和簡潔。
底層實現上,await/async實際上轉換成了promise,換句話說,await/async是promise的語法糖。每次我們使用await時,js解析器會生成一個promise,并將async函數中的剩余代碼放到then回調中去執行。
思考下面的例子:
async function f() { console.log("Starting F"); const result = await rp("http://example.com/"); console.log(result); }
下面描述函數f的基本計算過程。由于f是異步的,它會與調用方并行執行:
函數f開始執行,遇到await后生成一個promise。此時,函數的其余部分被封裝在回調中,并在promise完成后執行。
前面的大部分例子中,我們都是假設promise成功完成了。因此,等待promise返回一個值。如果我們等待的promise失敗了,在async函數中會導致一個異常。我們可以使用標準的try/catch來捕獲和處理它。
async function f() { try { const promiseResult = await Promise.reject("Error"); } catch (e){ console.log(e); } }
如果async函數沒有處理異常,不管是promise reject了,還是產生了其他bug,它都會返回一個rejected的promise對象。
async function f() { // Throws an exception const promiseResult = await Promise.reject("Error"); } // Will print "Error" f(). then(() => console.log("Success")). catch(err => console.log(err)) async function g() { throw "Error"; } // Will print "Error" g(). then(() => console.log("Success")). catch(err => console.log(err))
這給我們提供了一種簡便的方法,通過已知的異常處理機制來處理被rejected的promise。
討論async/await 在語言結構上是對promise的補充。但是,async/await 并不能取代純promise的需求。例如,在正常函數和全局作用域我們不能使用await,所以需要使用普通的promise:
async function fAsync() { // actual return value is Promise.resolve(5) return 5; } // can"t call "await fAsync()". Need to use then/catch fAsync().then(r => console.log(`result is ${r}`));
我通常會將異步邏輯封裝到一個或者少數幾個async函數中,然后在非異步代碼中調用async函數。這樣我可以最小化降低書寫then/catch的數量。
學者們指出,并發性和并行性是有區別的。并發性是指將獨立的進程(一般意義上的進程)組合在一起,而并行實際上是同時執行多個進程。并發性是關于應用程序設計和結構的,而并行性是關于實際執行的。
我們以一個多線程應用程序為例。應用程序分離到線程定義了它的并發模型。這些線程在可用內核上的映射定義了它的級別或并行性。并發系統可以在單個處理器上高效運行,在這種情況下,它不是并行的。
就此而言,promise允許我們將一個程序分解為并行的并發模塊,也可以不并行運行。實際的JavaScript執行是否并行取決于實現。例如,Node Js是單線程的,如果一個promise是CPU綁定的,你就不會看到太多的并行性。然而,如果你通過像Nashorn這樣的東西把你的代碼編譯成java字節碼,理論上你可能能夠在不同核心上映射CPU綁定的promise,并且實現并行性。因此,在我看來,promise(無論是普通的或通過await/async)構成了JavaScript應用程序的并發模型。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89634.html
摘要:編寫組件時要考慮的基本準則是單一職責原則。這些更改通常要求組件在隔離狀態下易于修改這也是的目標。解決多重責任問題需要將分割為兩個組件和。組件之間的通信是通過實現。更改的唯一原因是修改表單字段。 翻譯:劉小夕原文鏈接:https://dmitripavlutin.com/7-... 原文的篇幅非常長,不過內容太過于吸引我,還是忍不住要翻譯出來。此篇文章對編寫可重用和可維護的React組...
摘要:原文地址原文作者翻譯作者是在版本中引入的,它對于中的異步編程而言是一個巨大的提升。可能會產生誤導一些文章把和進行了比較,同時說它是異步編程演變過程中的下一代解決方案,對此我不敢茍同。結論在中引入的關鍵字無疑是對異步編程的一大加強。 原文地址: https://hackernoon.com/javasc...原文作者: Charlee Li 翻譯作者: Xixi20160512 asy...
摘要:即為裝飾器函數的這里主要為了獲取路由路徑的前綴,為請求方法,為請求路徑,為請求執行的函數。下邊是設置路由路徑前綴和塞入內容的裝飾器函數就不多說了,就是掛載前綴路徑到類的原型對象上,這里需要注意的是作用于類,所以是被修飾的類本身。 很多面對象語言中都有裝飾器(Decorator)函數的概念,Javascript語言的ES7標準中也提及了Decorator,個人認為裝飾器是和async/a...
摘要:想閱讀更多優質文章請猛戳博客一年百來篇優質文章等著你引入的在的異步編程中是一個極好的改進。可能會產生誤導一些文章將與進行了比較,并聲稱它是下一代異步編程風格,對此作者深表異議。結論引入的關鍵字無疑是對異步編程的改進。 showImg(https://segmentfault.com/img/bVbjFP0?w=800&h=450); 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇...
閱讀 3513·2021-11-17 17:01
閱讀 3918·2021-11-08 13:12
閱讀 2477·2021-10-08 10:04
閱讀 687·2021-09-29 09:35
閱讀 1418·2021-09-26 10:12
閱讀 2021·2021-09-07 09:58
閱讀 1953·2019-08-30 15:55
閱讀 2134·2019-08-30 13:14