本系列屬於阮一峯老師所著的ECMAScript 6 入門學習筆記javascript
ES2017中引入了async函數,使異步操做變得更加方便。async是Generator函數的語法糖。java
const gen = function* (){ const f1 = yield readFile('/etc/fstab') const f2 = yield readFile('/etc/shells') } // 寫成async函數 const gen = async function (){ const f1 = await readFile('/etc/fstab') const f2 = await readFile('/etc/shells') }
比較可知,async
函數將Generator函數的*
替換成async
,將yield
替換成await
,僅此而已es6
async
函數對Generator函數的改進有如下四點:shell
(1)內置執行器promise
Generator函數執行須要依靠執行器,而async
函數自帶執行器。也就是說async
函數的執行與普通函數同樣併發
gen()
(2)更好的語義化異步
async
和await
比起*
和yield
,語義更加清楚。async
表示函數有異步操做,await
表示緊跟在後面的表達式須要等待結果async
(3)更廣的適用性函數
co
模塊約定,yield
命令後面只能是Thunk函數或者Promise對象,而async
函數的await
命令後面能夠是Promise對象和原始類型的值(數值、字符串和布爾值,但這等同於同步操做)學習
(4)返回值是Promise
async
函數的返回值是Promise對象,這比Generator函數的返回值是Iterator對象方便多了,咱們能夠用then
指定下一步的操做
async
函數會返回一個Promise對象,能夠使用then
方法添加回調函數。當函數執行的時候,一旦遇到await
就會先返回,等到異步操做完成,再接着執行函數體後面的語句。
function timeout(ms){ return new Promise(resolve =>{ setTimeout(resolve,ms) }) } // 因爲async函數返回的是Promise函數,所以可改寫爲 async function timeout(ms){ await new Promise(resolve =>{ setTimeout(resolve,ms) }) } async function asyncPrint(value,ms){ await timeout(ms) console.log(value) } asyncPrint('hello world',50) // 在50毫秒以後輸出hello world // async函數的多種使用形式 // 函數聲明 async function foo(){} // 函數表達式 const foo = async function(){} // 對象的方法 let obj = {async foo(){}}; obj.foo().then() // class的方法 class Storage{ constructor(){ this.cachePromise = caches.open('avatars') } async getAvatar(name){ const cache = await this.cachePromise return cache.match(`/avatars/${name}.jpg`) } } const storage = new Storage() storage.getAvatar('jake').then() // 箭頭函數 const foo = async () => {}
// async函數內部return語句返回的值,會成爲then方法回調函數的參數 async function f(){ return 'hello world' } f().then(v => console.log(v)) // 'hello world' // async函數內部拋出錯誤,會致使返回的Promise對象變成reject狀態,拋出的錯誤對象被catch回調函數接收 async function f(){ throw new Error('出錯了') } f().then(v => console.log(v),e => console.log(e)) // Error:出錯了 // async函數返回的Promise對象,必須等內部全部的await命令後面的Promise對象執行完,纔會發生狀態改變,除非遇到return語句或者拋出錯誤
// 正常狀況下,await命令後面是一個Promise對象。若是不是,會被轉成一個當即resolve的Promise對象 async function f(){ return await 123 } f().then(v => console.log(v)) // await後面的Promise對象變爲reject狀態,則reject的參數會被catch方法的回調函數接收到 async function f(){ await Promise.reject('出錯了') } f().then(v => console.log(v)).catch(e => console.log(e)) // 出錯了 // 只要有一個await語句後面的Promise變爲reject,那麼整個async函數都會中斷執行 async function f(){ await Promise.reject('出錯了') await Promise.resolve('Hello world') // 不會執行 } // 若是但願前一個異步操做失敗,也不中斷後面的異步操做,這時能夠把第一個await放在try...catch結構裏面,這樣不管這個異步操做是否成功,第二個await都會執行 async function f(){ try{ await Promise.reject('出錯了') }catch(e){ } return await Promise.resolve('hello world') } f().then(v => console.log(v)) // 'hello world' // 另一種方法是await後面的Promise對象再跟一個catch方法,處理前面可能出現的錯誤 async function f(){ await Promise.reject('出錯了').catch(e => console.log(e)) return await Promise.resolve('hello world') } f().then(v => console.log(v)) // 出錯了 hello world // 若是有多個await命令,能夠統一放在try...catch結構中
(1)作好錯誤處理,最好把await
命令放在try...catch
代碼塊中
(2)多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發
let foo = await getFoo() let bar = await getBar() // 這兩個獨立的異步操做互不影響,被寫成繼發關係,這樣比較耗時,可讓他們同時觸發,縮短程序的執行時間 // 寫法一 let [foo,bar] = await Promise.all([getFoo(),getBar()]) // 寫法二 let fooPromise = getFoo() let barPromise = getBar() let foo = await fooPromise let bar = await barPromise
(3)await
只能用在async
函數中,若是用在普通函數,就會報錯
async函數的實現原理,就是將Generator函數和自動執行器,包裝在一個函數裏
async function fn(args){ // ... } // 等同於 function fn(args){ return spawn(function* (){ // ... }) } // 其中spawn函數就是自動執行器
如下例子比較async函數、Promise、Generator函數
// 某個DOM元素,部署了一系列的動畫,前一個動畫結束,纔開始後一個。若是其中有一個動畫出錯,就再也不繼續執行了,返回上一個成功執行的動畫的返回值 // Promise的寫法 function chainAnimationsPromise(elem,animations){ // 變量ret用來保存上一個動畫的返回值 let ret = null // 新建一個空的Promise let p = Promise.resolve() // 使用then方法,添加全部動畫 for(let anim of animations){ p = p.then(function(val){ ret = val return anim(elem) }) } // 返回一個部署了錯誤機制的Promise return p.catch(function(e){ // 忽略錯誤,繼續執行 }).then(function(){ return ret }) } // Promise寫法已經比回調函數的寫法大大改進,但操做自己的語義不太容易看出來 // Generator函數的寫法 function chainAnimationsGenerator(elem,animations){ return spawn(function* (){ let ret = null try{ for(let anim of animations){ ret = yield anim(elem) } }catch(e){ // 忽略錯誤,繼續執行 } return ret }) } // Generator函數須要一個spawn自動執行器,並且必須保證yield語句後面的表達式必須返回一個Promise // async函數的寫法 async function chainAnimationsAsync(elem,animations){ let ret = null try{ for(let anim of animations){ ret = await anim(elem) } }catch(e){ //忽略錯誤,繼續執行 } return ret } // async函數的實現最簡潔,最符合語義,代碼量少
// 一次遠程讀取一組URL,按照讀取的順序輸出結果 // Promise的寫法 function logInOrder(urls){ // 遠程讀取全部URL const textPromises = urls.map(url =>{ return fetch(url.then(response => response.text())) }) // 按次序輸出 textPromises.reduce((chain,textPromise) => { return chain.then(() => textPromise).then(text => console.log(text)) },Promise.resolve()) } // async函數寫法 async function logInOrder(urls){ for(const url of urls){ const response = await fetch(url) console.log(await response.text()) } } // 這樣簡化了寫法,可是全部操做都是繼發,效率差,咱們須要併發請求 async function logInOrder(urls){ // 併發讀取遠程URL const textPromises = urls.map(async url =>{ const response = await fetch(url) return reponse.text() }) // 按次序輸出 for(const textPromise of textPromises){ console.log(await textPromise) } } // map方法的參數是async函數,但他是併發執行的,由於只有async函數內部是繼發執行,外部不受影響。後面的for...of循環內部使用await,所以實現了按順序輸出