Async函數是Generator函數的語法糖
async函數本質上是對generator函數的封裝,使其更加有語義化,更加簡潔,調用更加方便,其主要優化是如下幾點:javascript
-
內置執行器。即async函數不須要像generator函數同樣每次都須要調用
.next()
方法去執行,它封裝了內部的執行器能夠幫咱們自動的執行.next()
。java -
更好的語義化。async...await...語法使代碼閱讀更加有語義化。es6
-
更廣的適用性。co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的promise
await命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,異步
但這時等同於同步操做)。async
-
返回值是 Promise。async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象函數
方便多了。你能夠用then方法指定下一步的操做。進一步說,async函數徹底能夠看優化
做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的動畫
語法糖。this
// async與generator的寫法對比 const hello = function (name) { console.log(`hello ${name}`);; }; const say = function* () { yield hello('xiaoming'); yield hello('aiMi'); }; let g = say(); g.next(); g.next(); // 踩了一個坑,generator函數必需要先調用而且賦值給一個變量,才能夠next,先調用不會執行,但會返回一個指向內部狀態的指針對象(遍歷器對象(Iterator Object)) // say().next(); const asyncSay = async function () { await hello('xiaoming'); await hello('aiMi'); }; let result = asyncSay(); console.log(result); // Promise {<pending>}
Async函數基本使用
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 () => {};
一個簡單demo來看下async函數在宏任務與微任務之間的表現
async function asyncTest1() { console.log('2'); await console.log('3'); } async function asyncTest2() { await console.log('4'); console.log('5'); } async function asyncTest3() { await new Promise(function(resolve) { console.log('6'); resolve(); }).then(function() { console.log('7'); }); console.log('8'); } console.log('1'); setTimeout(function() { console.log('9'); new Promise(function(resolve) { console.log('10'); resolve(); }).then(function() { console.log('11'); }) asyncTest1(); }); asyncTest2(); asyncTest3(); new Promise(function(resolve) { console.log('12'); resolve(); }).then(function() { console.log('13'); }); // 打印順序 1==>4==>6==>12==>5==>7==>13==>8==>9==>10==>2==>3==>11
借用大神的一張圖來理解一下js的執行順序,一般咱們任務分爲同步與異步,通常同步任務進入主線程,異步任務進入event table
並完成指定的事情之後註冊回調函數進入event queue
,當主線程的任務執行完畢之後會從event queue
去取異步任務進入主線程。(ps:我的理解這裏其實會遵循js單線程的原則,執行的一直是主線程。)這時候任務又會進行細分,分爲宏任務與微任務,宏任務與微任務分別存放在不一樣的event queue
中,當主線程完成之後會先取微任務中的任務,當微任務中的任務執行完之後再執行宏任務隊列中的任務,全都完成之後會繼續循環次操做(事件輪詢)。
結合上述描述分析上面代碼,由於以前不太肯定await後邊會被分配在什麼任務中,執行之後分析發現await後的內容能夠看作是一個promise對象中的resolve,因此是當即執行的,屬於主線程,await之後的會被丟進微任務中。
那麼咱們能夠肯定首先執行主線程中的任務即1,4,6,12;這時候settimeout中的內容被丟進宏任務,5,7,8,13被丟進微任務,又由於8是在await的promise對象的then以後,await的做用是其後的代碼執行完畢之後才執行下一行代碼,因此8被丟進了更後一層的微任務,因此如今會先按順序執行微任務5,7,13,而後執行完之後再執行8;當這次循環匯中的微任務執行完之後開始執行宏任務,將宏任務中的內容歸入主線程,按照以前的方式先執行主線程,遇到異步將異步丟進event table
,因此按順序執行時9,10,2,3,最後剩下promise的then在微任務中,最後執行11。
Async函數使用注意
-
若是await後面的異步操做出錯,那麼等同於async函數返回的 Promise 對象被reject。因此最好把await命令放在try...catch代碼塊中。
-
多個await命令後面的異步操做,若是不存在繼發關係,最好讓它們同時觸發。
-
await命令只能用在async函數之中,若是用在普通函數,就會報錯。
-
async 函數能夠保留運行堆棧。
-
還有一點須要注意的是咱們經過上邊那段demo代碼能夠發現若是你await後邊跟的是一個沒有
.then()
的promise對象其實仍是至關於同步,因此要想要獲得await後的代碼全都執行完畢的結果必定要將異步代碼放到.then()
中並暴露出來,這樣await後的代碼纔會在其異步全都執行完之後再執行。
Async函數的實現原理
async function fn(args) { // ... } // 等同於 function fn(args) { return spawn(function* () { // ... }); } 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); }); }); }
與其餘異步寫法對比
// 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; }); } // 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; }); } // async 函數 async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略錯誤,繼續執行 */ } return ret; }
經過對比咱們能夠發現async函數寫法簡潔語義明確,能夠極大地減小代碼量,而且讓代碼的可讀性提升。在阮大神的《ECMAScript 6 入門》中還介紹了異步遍歷器,for await...of,異步generator等等,感興趣能夠再詳細研究一下。