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狀態時,相應的協程會交出控制權,進入等待狀態。這個是本質。

  • awaitasync 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/@peterchang\_82818/javascript-es7-async-await-%E6%95%99%E5%AD%B8-703473854f29-tutorial-example-703473854f29

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/

results matching ""

    No results matching ""