async函數其實就是以前說過的Generator的語法糖,用於實現異步操做。它是ES2017的新標準。數組
讀取兩個文件:併發
const fs = require('fs') const readFile = function(filename){ return new Promise(function(resolve,reject){ fs.readFile(filename,function(error,data)){ if(error) return reject(error) resolve(data) } }) } const gen = function* (){ const gen1 = yield readFile('../1.txt') const gen2 = yield readFile('../2.txt') console.log(gen1.toString()) console.log(gen2.toString()) }
若是使用async函數的話,會是這麼寫異步
const _readFile = async function(){ const r1 = await readFile('../3.txt') const r2 = await readFile('../4.txt') console.log(r1.toString()) console.log(r2.toString()) }
通常狀況下,只是把Generator的*換成async,把yield換成await。
async函數是Generator函數的改進,具體體如今四點async
1.內置執行器:函數
相對於Generator函數須要co模塊或者next()方法來做爲執行器執行,async函數自帶執行器,因此上邊的代碼只須要一句fetch
_readFile()
能夠執行。ui
2.更好的語義:url
async和await分別表示異步和等待,比起*和yield更容易理解。spa
3.更普遍的適用性:
yield後邊只能跟Thunk函數或者Promise對象,可是在async函數中,能夠跟Promise對象和基本數據類型。code
4.返回值是Promise
相比於Generator函數返回一個Iterator還須要遍歷,async直接返回一個Promise能夠直接調用then方法和catch方法。
async函數返回一個Promise對象,能夠使用then方法添加回調,而後使用await關鍵字後,會等到異步操做執行完在執行後邊的語句。
async function getPriceByName(name){ const symbol = await getSymbod(name) const price = await getPrice(symbol) return price } getPriceByName('WUBA').then(function(result){ console.log(result) })
上邊的例子,是一個經過股票的名稱,得到股票的代碼,再得到價錢。前邊聲明async關鍵字,表示內部有內部操做,調用函數會返回一個Promise對象。
再看下一個例子,是一個指定多少毫秒後返回一個值。
function TimeOut(time){ return new Promise(function(resolve)){ setTimeout(resolve,time) } } async asyncTimeOut = function(value,time){ const t1 = await TimeOut(time) console.log(t1) } asyncTimeOut('hello',1000) asyncTimeOut('world',2000)
async函數的多種形式
函數表達式 const fun1 = async function(){ .... } 函數聲明 async function fun2(){ .... } 箭頭函數 const fun3 = async () => { .... } 對象中的變量 let obj = { async fun4(){ .... } } obj.fun4() 類的寫法 class Async { constructor(){} async fun5(){ .... } } const a1 = new Async() a1.fun5().then()
說過不少次了,async函數返回一個Promise對象。
函數return的值將會做爲then方法的參數
async function show(){ return '123' } show().then((v) => console.log(v))
show方法返回的值會被做爲then方法的參數而調用。
若是在async函數內部拋出錯誤,會被catch捕獲,這個時候Promise對象變成reject狀態。
async function show2(){ throw new Error('出錯了') } show2().then( v => console.log(v), e => console.log(e) )
async函數返回的Promise對象,必須等到函數內部全部的await執行完纔會發生狀態的變化,也就是說,得等到全部await執行完,纔會執行後續的then方法。
async function getText(url){ const response = await fetch(url) const text = await response.text() return text.match('../aa/[a-z]}')[1] //反正就是一個正則匹配 } const url = '....' getText(url).then(v => console.log(v))
這個例子說明,得等到兩個awiat都執行完纔會console返回的數據。
前邊說過,await命令後邊跟隨一個Promise對象。若是不是,會被轉成Promise對象。
async function show(){ return await '123' } show().then((v) => console.log(v))
若是await後邊的Promise對象變成了reject狀態,會被後邊的catch()捕獲。
async function fun1() { return await Promise.reject('出錯了') } fun1().catch(e => console.log(e))
若是async函數內部有多個await,可是隻要一個await返回的Promise對象變成了reject狀態,則整個函數馬上捕獲異常。
若是想要前邊的正常拋出異常而不影響後邊的await語句執行,能夠把前邊的寫進一個try/catch中去。
async function fun2(){ try{ await Promise.reject('出錯了') }catch(e){ } await Promise.resolev('hello') } fun2().then(v => console.log(v))
因爲await後面跟隨的是Promise對象,因此對象可能會有兩個狀態,一個resolve一個reject。因此,最好把await代碼放到try/catch語句中比較好。
async function fun3(){ try{ await asyncFun1() await asyncFun2() } catch(e){ console.log(e) } } // 還有另一種寫法 async function fun4(){ await asyncFun1().catch(e => console.log(e)) await asyncFun2().catch(e => console.log(e)) }
仍是第一種方法更好一點。直接寫進try/catch語句中。
最好讓多個await後邊的異步操做同時發生,若是不是不存在前後順序的話。
let a1 = await get1() let a2 = await get2()
上邊的寫法,get1執行完以後纔會執行get2,若是get1和get2沒有直接的關聯,那樣會很浪費時間。
//同時觸發 let [a1,a2] = await Promise.all([get1(),get2()])
若是await放到async函數以外,就會報錯,只能放到async函數內部。
原理也很簡單,就是把Generator函數和自動執行器包裝在一個函數中。
async function fn(){ ..... } // 等價於 function fn(){ return spawn(function *(){ ..... }) }
其中spawn函數就是自動執行器。
function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
實際開發中常常會遇到各類異步操做,這裏有一個例子。一次讀取一組url,而後按照讀取的順序返回結果。
function readUrls(urls) { const textPromise = urls.map(url => { fetch(url).then(response => response.text()) }) // 按照順序讀出 textPromise.reduce((chain,textPromise) => { return chain.then(() => textPromise) .then(text => console.log(text)); },Promise.resolve()) }
分析一下,上邊的代碼,用fetch同時讀取一種url,每一個fetch操做都返回一個Promise對象,放入textPromise數組,而後reduce方法一次處理每一個Promise對象,而後用then鏈接起來,一次輸出結果。
缺點:這種方法看起來不太好理解,不太直觀,用async函數會更好一點。
async function readUrls(urls){ for(const url of urls){ const response = await fetch(url) console.log(response.text()) } }
能夠看到,代碼是大大簡化了,可是會有一個新的問題,就是必須等到前邊一個讀完了,纔會讀取下一個數據。
function readUrls(urls){ const textPromise = urls.map(async url => { const response = await fetch(url) return response.text() }) } //按照順序輸出 for(const text of textPromise){ console.log(await text) }
上邊的函數,雖然map方法的參數是async函數,但倒是併發執行的,由於內部是繼發執行,不影響外部。在後邊的循環中使用了await,這樣,仍是會依次輸出。