JavaScript ES7中的 async/await
語法使得異步Promise變得更加容易。 若是您須要以某種順序從多個數據庫或API異步獲取數據,則可使用promise和回調構成的麪條式的代碼。 async/await
構造容許咱們更簡潔地表達這種邏輯且代碼更易讀和可維護。數據庫
本教程將使用圖表和簡單示例來解釋JavaScriptasync/await
語法和語義。編程
在咱們開始以前,讓咱們從一個Promise
的簡要概述開始。 若是您已經瞭解了JSPromise
,請隨時跳過本節。promise
在JavaScript中,Promises
表明非阻塞異步執行的抽象。 若是瞭解其餘語言的話,JSPromise與Java的Future
或C#的Task
相似。微信
Promises
一般用於網絡和I/O操做 - 例如從文件讀取或發出HTTP請求。 若是不須要阻塞當前的「線程」執行,咱們能夠產生一個異步Promises
,並使用then方法來傳入一個回調函數,它在promise完成時將被觸發。 回調函數自己能夠返回Promise
,所以咱們能夠鏈式調用Promise
。網絡
爲了簡單起見,在全部示例中,咱們假設request-promise
已經安裝並能夠像下面這樣子加載:併發
var rp = require('request-promise');
如今咱們能夠作一個簡單的HTTP GET請求,返回一個Promise
異步
const promise = rp('http://example.com/')
如今,咱們來看一個例子:async
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...");
咱們在第3行產生了一個新的Promise
,而後在第4行附加一個回調函數。Promise
是異步的,因此當咱們到達第6行時,咱們不知道Promise
是否已經完成。 若是咱們屢次運行代碼,咱們可能會每次獲得不一樣的結果。 更確切地說,任何承諾以後的代碼都是與Promise
同時運行的。ide
在Promise
完成以前,咱們沒有任何合理的理由阻止當前的操做順序。 這與Java的Future.get
不一樣,它容許咱們阻止當前線程,直到未來完成。 在JavaScript中,咱們不能等待Promise
完成。 在Promise
完成以後執行代碼的惟一方法是經過then
方法傳入回調函數。函數
下圖描繪了該示例的計算過程:
Promise
的計算過程。 調用「線程」不能等待Promise
。 在Promise
以後執行代碼的惟一方法是經過then
方法指定回調函數。
只有當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
和匿名回調能夠很容易失去對代碼的控制。
例如,假設咱們須要編寫一個程序:
如下代碼段演示瞭如何完成此操做:
// 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/'); const combinedPromise = Promise.all([call2Promise, call3Promise]); combinedPromise.then(arr => { // Executes after both promises have finished console.log(arr[0]); console.log(arr[1]); }) })
咱們首先發起了第一個HTTP請求,並在其完成時運行回調函數(第1-3行)。 在回調中,咱們爲後續的HTTP請求產生了兩個Promise
(第8-9行)。 這兩個Promise
同時運行,咱們須要安排一個回調,在它們都完成時調用。 所以,咱們須要經過Promise.all
(第11行)將它們組合成一個單一的Promise
,當它們完成時,它們就能夠正確調用。 而後咱們傳入了另外一個打印結果的回調(第14-15行)。
下圖描述了計算流程:
對於這樣一個簡單的例子,咱們最終獲得了2個嵌套的回調函數,而且必須使用Promise.all來同步併發Promise
。 若是咱們不得再也不運行一些異步操做或添加錯誤處理怎麼辦? 這種方法能夠很容易地改寫成用Promise.all
和多個then
鏈接起來的鏈式麪條代碼。
咱們能夠重寫上面的例子來使用「promise chaining」,以下所示:
// 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]); })
這樣可讀性更高一些,儘管咱們仍然須要連接兩個回調函數並使用Promise.all
。
Async函數是返回Promise
函數的簡寫。
例如,如下定義是等價的:
function f() { return Promise.resolve('TEST'); } // asyncF is equivalent to f! async function asyncF() { return 'TEST'; }
相似地,拋出異常的Async函數等效於返回reject Promise
的函數:
function f() { return Promise.reject('Error'); } // asyncF is equivalent to f! async function asyncF() { throw 'Error'; }
當咱們產生承諾時,咱們沒法同步等待完成。 咱們只能經過一個回調。 不容許等待承諾鼓勵開發非阻塞代碼。 不然,開發人員將被誘惑執行封鎖操做,由於它比使用承諾和回調更容易。
當咱們建立Promise
時,咱們沒法同步等待完成。 咱們只能經過一個回調。 不容許等待Promise
,鼓勵開發非阻塞代碼。 不然,開發人員將更容易使用鎖定當前線程的操做,由於它比使用Promise
和回調更容易。
然而,爲了同步Promise
,咱們須要容許他們相互等待。 換句話說,若是操做是異步的(即封裝在Promise
中),則應該可以等待另外一個異步操做完成。 可是JavaScript解釋器如何知道一個操做是否在Promise
中運行?
答案是在async
關鍵字。 每一個async
函數都返回一個Promise
。 所以,JavaScript解釋器知道async
函數中的全部操做都將被封裝在Promise
中並異步運行。 因此可讓他們等待其餘的Promise
完成以後再繼續執行。
當咱們使用await關鍵字。 它只能用於async
功能,並容許咱們同步等待Promise
。 若是咱們在async
函數以外使用Promise
,咱們仍然須要使用回調函數:
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'));
如今,咱們來看看咱們如何解決上一節的問題:
// 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
函數中。 這使咱們可以直接等待Promise
,從而避免了回調的須要。 最後,咱們調用async
函數,該函數只是產生一個封裝了調用其餘Promise
的邏輯的Promise
。
事實上,在第一個例子中(沒有async/await
),這些Promise
將會並行開始。 在這種狀況下,咱們作一樣的(7-8行)。 請注意,直到第11-12行,當咱們使用了await
,直到兩個Promise
都已經完成爲止。 以後,咱們知道這兩個Promise
都已經完成了(相似於前面的例子中使用Promise.all
(...)而後(...))。
實際計算過程等同於上一節所述的過程。 然而,代碼更加可讀和直觀。
在引導下,async/await
實際上轉化爲Promise
,而後回調。 換句話說,它是使用Promise
的語法糖。 每次咱們等待,解釋器產生一個Promise
,並將其他的操做從異步功能放在一個回調。
咱們來考慮下面的例子:
async function f() { console.log('Starting F'); const result = await rp('http://example.com/'); console.log(result); }
f函數的基礎計算過程以下所示。 因爲f是異步的,它也將與其調用者並行運行
函數f啓動併產生Promise
。 在那一刻,函數的其他部分被封裝在一個回調函數中,而且在Promise
完成以後計劃執行。
在前面的大多數例子中,咱們假設Promise
成功執行了。 所以,等待Promise
返回值。 若是咱們等待失敗的Promise
,這將致使異步功能中的異常。 咱們可使用標準的try/catch
來處理它:
async function f() { try { const promiseResult = await Promise.reject('Error'); } catch (e){ console.log(e); } }
若是async
函數不處理異常,不管是由拒絕Promise
仍是其餘錯誤引發的,都將返回被拒絕的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))
這經過已知的異常處理機制使咱們方便地處理被拒絕的Promise
。
Async/await
是一種對Promise
的語言上的補充。 它容許咱們以較少的樣板來使用Promise
。 可是,Async/await
不能取代純粹Promise
的須要。 例如,若是咱們從正常函數或全局範圍調用Async
函數,咱們將沒法使用await
,並將訴諸於vanillaPromise
:
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}`));
我一般會嘗試將大多數異步邏輯封裝在一個或幾個異步函數中,這是從非異步代碼中調用的。 這最大限度地減小了我須要編寫的try/catch
回調的數量。
Async/await
結構是更符合Promise
的語法糖。 每一個Async/await
結構能夠用簡單的Promise
重寫。 因此,這是一個風格和簡潔的問題。
關注個人微信公衆號,更多優質文章定時推送