JavaScript async和await 非同步操作
由於JavaScript的執行環境是單執行緒的,所以非同步操作尤為重要,否則不知道會卡成什麼樣子。
非同步操作通俗的講,就是將一件事情分為兩個階段來執行,最典型的就是ajax請求,當向伺服器發起請求之後,可以先去執行其他操作,無需等待這個請求過程,當請求完成,再去繼續處理請求發回的資料。
一.原有的非同步程式設計方式:
在ES2015之前,常見的非同步程式設計方式有以下幾種:
(1).回撥函式。
(2).釋出訂閱。
(3).事件監聽。
(4).Promise物件。
特別說明:Promise物件在ES2015之前是自定義實現的,並沒有被標準化。
二.ES2015新增非同步程式設計方式:
(1).Promise物件(ES2015對其進行了標準化)。
(2).Generator 函式。
三.優缺點簡單介紹:
簡單介紹一下原有非同步程式設計方式的優缺點。
JavaScript非同步程式設計的實現其實就是通過回撥函式來完成的。
首先看一個簡單程式碼片段:
[JavaScript] 純文字檢視 複製程式碼fs.readFile(fileA, function (err, data) { fs.readFile(fileB, function (err, data) { fs.readFile(fileC, function (err, data) { fs.readFile(fileD, function (err, data) { // ... }); }); }); });
首先強調一下,回撥函式方式的非同步程式設計沒有任何問題,但是在程式碼的可讀性方面極差,大家可以想象,如果回撥函式的巢狀有100層,將會是一個什麼樣的情景。為了解決這種問題,Promise物件出現應運而生(ES2015之前雖然已經大量應用,但是並沒有標準化),關於它的用法可以參閱JavaScript Promise 物件一章節。
[JavaScript] 純文字檢視 複製程式碼var readFile = require("fs-readfile-promise"); readFile(fileA) .then(function(data){ console.log(data.toString()); }).then(function(){ return readFile(fileB); }).then(function(data){ console.log(data.toString()); }).catch(function(err) { console.log(err); });
假定上面的程式碼中,readFile()都是非同步的檔案遠端請求操作;Generator函式之所以是良好的非同步容器,是因為yield語句會將執行權移出Generator函式,並且可以使用next()方法再次讓其獲得執行權。使用Generator函式可以使流程更加清晰,但是仍然不夠完美,因為無法自動將非同步操作全部執行完畢,當前可以使用co模組來解決此問題,簡單程式碼片段如下:
[JavaScript] 純文字檢視 複製程式碼var gen = function* (){ var f1 = yield readFile(fileA); var f2 = yield readFile(fileB); }; var co = require("co"); co(gen);
上面的程式碼可以使用co模組自動化實現各個非同步操作。
特別說明:co模組約定yield後面只能是Promise物件或者是一個接收回撥函式的函式(或稱其為Thunk函式)。
四.async 函式:
它是JavaScript非同步操作比較完美的解決方案,兼具Promise物件和Generator函式能夠避免回撥函式層層巢狀的窘境,又具有邏輯清晰和自動化執行所有非同步操作的優點。
特別說明:async函式是ES2016新增。
語法結構:
[JavaScript] 純文字檢視 複製程式碼async function [name]([param1[, param2[, ..., paramN]]]) { statements }
引數解析:
(1).name:可選,用來規定函式的名稱,省略的話就是一個匿名函式。
(2).paramN:可選,傳遞給函式的引數。
(3).statements:可選,函式體內容(如果省略的話也就沒啥意義了)。
看一個簡單的程式碼例項:
[JavaScript] 純文字檢視 複製程式碼async function func() { var x = await 10; var y = await 20; console.log(x); console.log(y); } func()
與普通的函式非常類似,下面簡單介紹一下它的特點:
(1).函式宣告的開頭需要使用async。
(2).函式內部具有await語句。
(3).呼叫方式與普通函式相同。
(4).能夠自動執行裡面的所有await語句,也就是所有非同步操作。
下面再來介紹一下await語句:
[JavaScript] 純文字檢視 複製程式碼await expression;
expression期待是一個Promise物件,如果不是Promise物件,將會被轉換成 resolved 後的promise。
等同於如下程式碼:
[JavaScript] 純文字檢視 複製程式碼Promise.resolve(expression)
await語句的返回值是它後面Promise物件resolved後的值,也就是傳遞給resolve()方法的引數值;如果promise被rejected,await 表示式會丟擲異常值。
特別說明:await語句不能用於普通函式中,否則會報錯。
程式碼例項:
[JavaScript] 純文字檢視 複製程式碼async function func() { return await 10; } func().then(function(data){ console.log(data) })
async函式執行後,會立即返回一個Promise物件,並等待此物件狀態的變化,它的狀態有內部的await語句後面Promise物件的狀態決定;then的回撥函式的接收的值是return語句返回的await語句返回值。
[JavaScript] 純文字檢視 複製程式碼async function func() { await 10; return await 20; } func().then(function(data){ console.log(data) })
async函式返回的Promise物件狀態的改變是等到所有await語句指定完畢之後。
[JavaScript] 純文字檢視 複製程式碼async function func() { await Promise.reject("螞蟻部落"); } func().then(function(resove){ console.log(resove) },function(reject){ console.log(reject) })
如果await後面的Promise變為reject狀態,則reject的引數會被catch回撥函式接收。
特別說明:如果是變為reject狀態,前面不加return,reject的引數也會被catch回撥函式接收。
[JavaScript] 純文字檢視 複製程式碼async function func() { await Promise.reject("螞蟻部落"); await 10; } f().then(function(resove){ console.log(resove) },function(reject){ console.log(reject) })
丟擲錯誤後,會中斷整個async函式的執行。
[JavaScript] 純文字檢視 複製程式碼async function func() { try { await Promise.reject("螞蟻部落一"); } catch(e) { } return await Promise.resolve("螞蟻部落二"); } func().then(function(resovle){ console.log(resovle) })
可以將可能丟擲錯誤的語句放入try catch語句中。
[JavaScript] 純文字檢視 複製程式碼async function func() { await Promise.reject("螞蟻部落一").catch(function(reject){ console.log(reject) }); return await Promise.resolve("螞蟻部落二"); } func().then(function(resovle){ console.log(resovle) })
也可以在可能丟擲錯誤的promise物件後面使用catch來捕獲丟擲的錯誤。
[JavaScript] 純文字檢視 複製程式碼async function func() { try { var valA = await a(); var valB = await b(); var valC = await c(); } catch (err) { console.error(err); } }
如果有多個await語句,那麼可以將其統一放在try語句中。
特別說明:如果具有多個await語句,且它們之間不是繼發關係,建議讓它們同時觸發,以達到最大效能:
[JavaScript] 純文字檢視 複製程式碼async function func(){ let aV = await a(); let bV = await b(); }
a和b是獨立的非同步操作,沒必要是繼發關係,也就是執行完a再去執行b,那麼可以採用以下方式:
[JavaScript] 純文字檢視 複製程式碼async function func(){ let [aV, bV] = await Promise.all([a(), b()]); }
具體可以參閱Promise.all()方法一章節;也可以採用下面的方式:
[JavaScript] 純文字檢視 複製程式碼async function func(){ let aP=a(); let bP=b(); let aV = await aP; let bV = await bP; }
建立兩個Promise物件,兩個幾乎同時開始非同步操作,不會等待a非同步操作完,然後在進行b的非同步操作。
相關文章
- [譯文] JavaScript async 和 awaitJavaScriptAI
- JavaScript async await 使用JavaScriptAI
- JavaScript Promises, async/awaitJavaScriptPromiseAI
- 理解JavaScript的async/awaitJavaScriptAI
- [Javascript] Promise question with async awaitJavaScriptPromiseAI
- async和awaitAI
- JavaScript 的 async/await 理解(4)JavaScriptAI
- 【譯】JavaScript中的async/awaitJavaScriptAI
- [譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async IteratorsJavaScriptSymbolAI
- [譯] JavaScript - Generator-Yield/Next 和 Async-AwaitJavaScriptAI
- JavaScript:async/await的基礎用法JavaScriptAI
- JavaScript中的async/await詳解JavaScriptAI
- [譯]JavaScript async / await:好處、坑和正確用法JavaScriptAI
- 20分鐘帶你掌握JavaScript Promise和 Async/AwaitJavaScriptPromiseAI
- async和await的使用AI
- JavaScript基礎——深入學習async/awaitJavaScriptAI
- 研究c#非同步操作async await狀態機的總結C#非同步AI
- Promise和async await詳解PromiseAI
- 如何講清楚async和await?AI
- JavaScript中async和await的使用以及佇列問題JavaScriptAI佇列
- Async +AwaitAI
- Async/awaitAI
- 學習JavaScript迴圈下的async/awaitJavaScriptAI
- 【JS基礎】從JavaScript中的for...of說起(下) - async和awaitJSJavaScriptAI
- Promise && async/await的理解和用法PromiseAI
- 理解 async/awaitAI
- 【-Flutter/Dart 語法補遺-】 sync* 和 async* 、yield 和yield* 、async 和 awaitFlutterDartAI
- async和await的錯誤捕獲AI
- JavaScript非同步程式設計–Generator函式、async、awaitJavaScript非同步程式設計函式AI
- 【譯】Async/Await(三)——Aysnc/Await模式AI模式
- async await詳解AI
- 淺談async/awaitAI
- async、await和generator函式內部原理AI函式
- async/await 和 promise/promise.all 的示例AIPromise
- Flutter非同步程式設計-async和awaitFlutter非同步程式設計AI
- JavaScript ES6 async/await的簡單學習demoJavaScriptAI
- JavaScript深入淺出非同步程式設計三、async、awaitJavaScript非同步程式設計AI
- async和await的使用總結 ~ 竟然一直用錯了c#中的async和await的使用。。AIC#