ES7 Async Await
Babel以及Node.js 7.6以上版本支援
先談generator和yield是要說明async/await的原理
async/await是generator, yield和promise的語法糖
var sum = 0
var first = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var second = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var third = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var fourth = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var fetchAll = async function () {
let a = await first() // 等待第一個請求執行結束,取回結果給a
console.log(a) // 1
sum += a // 加總
let b = await second() // 等待第二個請求執行結束,取回結果給b
console.log(b) // 1
sum += b // 加總
let c = await third() // 等待第三個請求執行結束,取回結果給c
console.log(c) // 1
sum += c // 加總
let d = await fourth() // 等待第四個請求執行結束,取回結果給c
console.log(d) // 1
sum += d // 加總
// 全部請求結束
console.log(sum) // 4 做最後處理
}
fetchAll()
處理併發請求
var first = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var second = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var third = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
var fourth = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(1)
}, 1000)
})
}
// 使用Promise.all([]),四個非同步請求會同時併發,等待全部都做完以後執行callback
Promise.all([first(), second(), third(), fourth()])
.then(function (result) {
// [1, 1, 1, 1]
console.log(result)
let sum = result.reduce((cur, next) => cur + next)
// 4
console.log(sum)
}).catch(console.error)
// 使用async/await
async function fetchAll () {
// 遠端抓取資料是可能失敗的! 記得用try/catch做error handling
try {
let result = await Promise.all([first(), second(), third(), fourth()])
// [1, 1, 1, 1]
console.log(result)
let sum = result.reduce((cur, next) => cur + next)
// 4
console.log(sum)
} catch (e) {
console.log(e)
}
}
fetchAll()
Async/Await真的比Promise好嗎? 是的。
Example 1:
const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)
// 使用Promise
function usePromise() {
let a readFile("a.txt", "utf8")
.then(tmp => {
a = tmp return readFile("b.txt", "utf8")
})
.then(b => {
let result = a + b console.log(result)輸出 "Hello, Fundebug!"
})
}
// 使用Async / Await
async function useAsyncAwait() {
let a = await readFile("a.txt", "utf8")
let b = await readFile("b.txt", "utf8")
let result = a + b console.log(result)輸出 "Hello, Fundebug!"
}
usePromise()
useAsyncAwait()
Example 2:
const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile) // 使用Promise
function usePromise() {
readFile("b.txt", "utf8").then(b => {
console.log(b)
})
}
// 使用Async/Await
async function useAsyncAwait() {
var b = await readFile("b.txt", "utf8")console.log(b) // 輸出"Fundebug!"
}
usePromise()
useAsyncAwait()
Example 3:
const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)
var files = ["a.txt", "b.txt"]
// 使用Promise
function usePromise() {
Promise
.map(files, file => {
return readFile(file, "utf8")
})
.then(results => {
console.log(results)
})
}
// 使用Async/Await
async function useAsyncAwait() {
var results = await Promise
.map(files, file => {
return readFile(file, "utf8")
})
console.log(results)
}
usePromise()
useAsyncAwait()
JavaScript的非同步編寫方式,從回撥函式到Promise再到Async/Await,表面上只是寫法的變化,本質上則是語言層的一次次抽象,讓我們可以 用更簡單的方式實現同樣的功能 ,而程式設計師不需要去考慮程式碼是如何執行的
//async with then
(async function() {
let res = await wait('wait 1 sec');
console.log(res);
return res;
})().then(async function() {
let res = await wait('wait 2 sec', 2000);
console.log(res);
return res;
})
//pure await
(async function() {
var res = await wait('wait 1 sec');
console.log(res);
res = await wait('wait 2 sec', 2000);
console.log(res);
})()
1- Constructor of async:asyncFunction
瞭解 async function 是什麼 Object,最好觀察它的 constructor ,呼叫一次就知道結果: asyncFunction Object (FromMozilla):
//Node.js
console.log(async function () {});
// Chrome or Firefox
console.log(async function () {}.constructor);
console.log(async function () {}.__proto__);
2- Constructing Promise function
噢!又是 Promises?為什麼一篇 async/await 的文章內要穿插著大量的其他內容?因為魔鬼在細節,async/await 是背後非同步的處理其實就是 Promise。這個觀念在開發、Module 設計和臭蟲Debug 特別有幫助,當別人接手舊人的程式碼,超多一半時間都花在捕捉臭蟲。
每一個 async 的建立和每一個 await 的等待都是操作 Promises 的小把戲:
var sleep = function(para) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(para * para)
}, 1000)
})
}
var result = await sleep(2)
// result is 4
3- Constructing Async function
通過async function
定義的 function/Object 就是一個回傳AsyncFunction
的 Object。
使用 async 處理非同步的 Promise function,回傳的值其實是 Resolve 的值;相似的, async exception 的結果和 Promise Reject 的結果也是一模一樣(Mozilla)。
async function asyncSleep (para){
return await sleep(para)
}
var result = await asyncSleep(2)
//result is 4
asyncSleep(3).then(function(result2){
//result2 is 9
})
4- Await Sequentially
var result1 = await sleep(2);
var result2 = await sleep(result1);
var result3 = await sleep(result2);
//result1 is 4
//result2 is 16
//result3 is 256
5- Await Parallelly
var results = await Promise.all([sleep(1), sleep(2)]);
//results is [1,4]
6- Nest
for(var i =0 ; i<3; i++){
var result = await sleep(i);
for(var j =0 ; j<result; j++){
console.log(' i:'+i+', j:'+j+': ', await sleep(j));
}
}
// i:1, j:0: 0
// i:2, j:0: 0
// i:2, j:1: 1
// i:2, j:2: 4
// i:2, j:3: 9
7- Dynamic Async functions
想通過 Promise 來解決 Dynamic 運算的問題並不容易,往往需要改變䇿略或安裝其他 Module。雖然 Async/Await 的功能強大,但是對處理 Dynamic 的問題也沒有現成的方法。
目前的解決方法是,加入 ES6 的新功能 map(),在儲存 Promises function 的同時把 parameters 也存起來,在需要執行時再進行呼叫。
var sleep = function(para1,para2) {
var _para1 = para1, _para2 = para2 || para1 ;
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(_para1 * _para2)
}, 1000)
})
}
var proMap = new Map();
proMap.set([1], sleep);
proMap.set([2, 3], sleep);
proMap.set([3], sleep);
for (var [para, fun] of proMap.entries()) {
var result = await fun.apply(this, para);
console.log(para, result)
}
//[ 1 ] 1
//[ 2, 3 ] 6
//[ 3 ] 9
8- Error handle
錯誤處理的聲音實在安靜,安靜得聽不見 (
Nolan Lawson
)
上面是一句在Nolan Lawson文章內名句,我非常認同。
同媽生下的 Async 和 Promises 在處理 Error Handling 的行為上,完全是執行同一模式,程式都必需必包在 try/catch內錯誤才能被捕獲。
var errorSleep = function(para) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(' ErrorSleep')
}, 1000)
})
}
try {
var result1 = await sleep(1);
var result2 = await errorSleep(4);
var result3 = await sleep(1);
console.log('result1: ', result1)
console.log('result2: ', result2)
console.log('result3: ', result3)
} catch (err) {
console.log('err: ', err)
console.log('result1: ', result1)
console.log('result2: ', result2)
console.log('result3: ', result3)
}
//err: ErrorSleep
//result1: 1
//result2: undefined
//result3: undefined
Sumary:
好處
async/await 的貢獻在語法上為 Javascripte 進行大優化,原本執行多行 Promises 程式簡化成一行,不僅僅提高程式的可讀性,也是為 functional programming 量身訂造的設計。
壞處
正如第7和8部份提到,Promises 在處理 Dynamic Asynchronize 沒問題沒有進步。只能透過較迂迴的路,使用其他 iterable 方法分別儲存 parameters和 function 。
Async/Await 的另一個問題,錯誤處理的聲音實在太安靜,聽不見。開發者多麼希望,當 Error發生時,JS 能夠大聲大聲的呼叫我!
用 await 取代 promise.then() 的使用方式
前面說到可以運用
Promise.all()
方法來等待所有的非同步工作完成,但最終還是回到了 callback 的模式進行等待。而且,總是有懶惰鬼開發者會把這些程式碼寫成一行,非常不好讀:
Promise.all(arr.map(async (num) => {
// 等待非同步工作完成
await asyncWorker(num);
return num + 1;
})).then((results) => {
// [ 2, 3, 4, 5, 6 ]
console.log(results);
});
既然已經有 async/await 的環境,很多人會儘量讓自己的 context 處於 async 函數的環境之下,這時我們就可以用 await 來取代 Promise 的 .then() 方法:
const main = async () => {
// 改用 await 等待 Promise 內的工作全部完成
let results = await Promise.all(arr.map(async (num) => {
// 等待非同步工作完成
await asyncWorker(num);
return num + 1;
}));
// [ 2, 3, 4, 5, 6 ]
console.log(results);
};
main();
await
關鍵字使用時有哪些注意點?
只能放在
async
函數內部使用,不能放在普通函數裡面,否則會報錯。後面放
Promise
對象,在Pending
狀態時,相應的協程會交出控制權,進入等待狀態。這個是本質。await
是async wait
的意思,wait
的是resolve(data)
消息,並把數據data
返回。比如,下面代碼中,當Promise
對象由Pending
變為Resolved
的時候,變量a
就等於data
;然後再順序執行下面的語句console.log(a);
這真的是等待,真的是順序執行,表現和同步代碼幾乎一模一樣。
await
後面也可以跟同步代碼,不過系統會自動轉化成一個Promise
對象。
比如const a = await 'hello world';
其實就相當於const a = await Promise.resolve('hello world');
這跟同步代碼const a = 'hello world';
是一樣的,還不如省點事,去掉這裡的await
關鍵字。
await
只關心異步過程成功的消息resolve(data)
,拿到相應的數據data
。至於失敗消息reject(error)
,不關心,不處理。
當然對於錯誤消息的處理,有以下幾種方法供選擇:
(1)讓await
後面的Promise
對象自己catch
(2)也可以讓外面的async
函數返回的Promise
對象統一catch
(3)像同步代碼一樣,放在一個try...catch
結構中
async
關鍵字使用時有哪些注意點?
有了這個
async
關鍵字,只是表明裡面可能有異步過程,裡面可以有await
關鍵字。當然,全部是同步代碼也沒關係。當然,這時候這個async
關鍵字就顯得多餘了。不是不能加,而是不應該加。async
函數,如果裡面有異步過程,會等待;
但是async
函數本身會馬上返回,不會阻塞當前線程。
可以簡單認為,
async
函數工作在主線程,同步執行,不會阻塞界面渲染。async
函數內部由async
關鍵字修飾的異步過程,工作在相應的協程上,會阻塞等待異步任務的完成再返回。
async
函數的返回值是一個Promise
對象,這個是和普通函數本質不同的地方。這也是使用時重點注意的地方
(1)return newPromise();
這個符合async
函數本意;
(2)return data;
這個是同步函數的寫法,這裡是要特別注意的。這個時候,其實就相當於Promise.resolve(data);
還是一個Promise
對象。
在調用async
函數的地方通過簡單的=
是拿不到這個data
的。
那麼怎麼樣拿到這個data
呢?
很簡單,返回值是一個Promise
對象,用.then(data => { })
函數就可以。
(3)如果沒有返回,相當於返回了Promise.resolve(undefined);
await
是不管異步過程的reject(error)
消息的,async
函數返回的這個Promise
對象的catch
函數就負責統一抓取內部所有異步過程的錯誤。async
函數內部只要有一個異步過程發生錯誤,整個執行過程就中斷,這個返回的Promise
對象的catch
就能抓到這個錯誤。async
函數執行和普通函數一樣,函數名帶個()就可以了,參數個數隨意,沒有限制;也需要有async
關鍵字。
只是返回值是一個Promise
對象,可以用then函數得到返回值,用catch抓去整個流程中發生的錯誤。
基本套路
Step1:用Promise
對象包裝異步過程,這個和Promise
的使用一樣。只是參數個數隨意,沒有限制。
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('sleep for ' + ms + ' ms');
}, ms);
});
}
Step2:定義異步流程,可以將按照需要定製,就像寫同步代碼那樣
async function asyncFunction() {
console.time('asyncFunction total executing:');
const sleep1 = await sleep(2000);
console.log('sleep1: ' + sleep1);
const [sleep2, sleep3, sleep4]= await Promise.all([sleep(2000), sleep(1000), sleep(1500)]);
console.log('sleep2: ' + sleep2);
console.log('sleep3: ' + sleep3);
console.log('sleep4: ' + sleep4);
const sleepRace = await Promise.race([sleep(3000), sleep(1000), sleep(1000)]);
console.log('sleep race: ' + sleepRace);
console.timeEnd('asyncFunction total executing:');
return 'asyncFunction done.' // 這個可以不返回,這裡只是做個標記,為了顯示流程
}
Step3:像普通函數調用async
函數,在then
函數中獲取整個流程的返回信息,在catch
函數統一處理出錯信息
asyncFunction().then(data => {
console.log(data); // asyncFunction return 的內容在這裡獲取
}).catch(error => {
console.log(error); // asyncFunction 的錯誤統一在這裡抓取
});
console.log('after asyncFunction code executing....'); // 這個代表asyncFunction函數後的代碼,
// 顯示asyncFunction本身會立即返回,不會阻塞主線程
流程解析
after asyncFunction code executing....
sleep1: sleep for 2000 ms
sleep2: sleep for 2000 ms
sleep3: sleep for 1000 ms
sleep4: sleep for 1500 ms
sleep race: sleep for 1000 ms
asyncFunction total executing:: 5006.276123046875ms
asyncFunction done.
異常處理
async
標註過的函數,返回一個Promise
對象,採用.then().catch()
的方式來進行異常處理,是非常自然的方法,也推薦這麼做。就像上面的step3
那樣做。另外一種方法,就是對於異步過程採用
await
關鍵字,採用同步的try{} catch(){}
的方式來進行異常處理。這裡要注意的是
await
關鍵字只能用在async標註的函數中,所以,原來的函數,不管以前是同步的還是異步的,都要加上async
關鍵字,比如componentDidMount()
就要變為async componentDidMount()
才可以在內部使用await
關鍵字,不過功能上沒有任何影響。另外,採用同步的
try{} catch(){}
的方式,可以把同步,異步代碼都可以放在裡面,有錯誤都能抓到,比如null.length
這種,也能抓到。
async componentDidMount() { // 這是React Native的回調函數,加個async關鍵字,沒有任何影響,但是可以用await關鍵字
// 將異步和同步的代碼放在一個try..catch中,異常都能抓到
try {
let array = null;
let data = await asyncFunction(); // 這裡用await關鍵字,就能拿到結果值;否則,沒有await的話,只能拿到Promise對象
if (array.length > 0) { // 這裡會拋出異常,下面的catch也能抓到
array.push(data);
}
} catch (error) {
alert(JSON.stringify(error))
}
}
參考連結:
https://itw01.com/QQVLERL.html
https://blog.fundebug.com/2017/04/04/nodejs-async-await/
http://www.webhek.com/post/javascript-await-async.html
http://www.webhek.com/post/javascript-async-await-2.html
https://www.jianshu.com/p/ffa5cbe9ab29
http://fred-zone.blogspot.tw/2017/04/javascript-asyncawait.html
http://fred-zone.blogspot.tw/2016/07/javascript-async.html
https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
https://mlwmlw.org/2016/03/es7-%E7%9A%84-async-await-%E5%85%A5%E9%96%80/