1. 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 () => {};
2. async函數的返回值總是一個Promise
無論async函數有無await操作,其總是返回一個Promise。
1. 沒有顯式return,相當於return Promise.resolve(undefined);
2. return非Promise的數據data,相當於return Promise.resolve(data);
3. return Promise, 會得到Promise對象本身
async總是返回Promise,因此,其后面可以直接調用then方法,
函數內部return返回的值,會成為then回調函數的參數
函數內部拋出的錯誤,會被then的第二個函數或catch方法捕獲到
//正常返回值 async function f(){ retrun 'hello world'; } f().then(v => console.log(v));//hello world //拋出錯誤 async function f(){ throw new Error('出錯了'); } f().then( v => console.log(v), e => console.log(e) //Error: 出錯了 )
3. await操作符的值
[rv] = await expression(expression可以是任何值,通常是一個promise)
expression是Promise,rv等於Promise兌現的值,若Promise被拒絕,則拋出異常,由catch捕獲
expression是非Promise,會被轉換為立即resolve的Promise,rv等於expression
await操作只能用在async函數中,否則會報錯。
4. async就是generator和promise的語法糖
//generator寫法 var gen = function* () { var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; //async寫法 var asyncReadFile = async function () { var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
async就是將 generator的 * 換成 async,將 yield 換成 await。
5. async對generator的改進
1. 內置執行器
Generator必須依靠執行器調用next方法來自動執行,例如co模塊。而async函數自帶執行器,可以自動執行。
2. 更好的語義
async和await分別表示異步和等待,語義更加明確
3. 適用性更強
co模塊后面只能是Thunk函數或Promise對象,而await后面可以是Promise或基本數據類型(如:數字,字符串,布爾等)
4. 返回Promise,可以繼續操作
async函數總是返回一個Promise對象,可以對其進行then調用,繼續操作后面的數據,因此,
async函數完全可以看作是多個Promise合成一個Promise對象,而await命令就是內部的then調用。
6. async內部的並行調用
async配合await都是串行調用,但是若有並行調用,則應按照以下方式來寫:
1. 變量分別接收Promise
let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise(); let bar = await barPromise();
2. 使用Promise.all
let [foo,bar] = await Promise.all([getFoo(),getBar()]);
Promise.all這種寫法有缺陷,一個調用報錯,會終止,這個不太符合並行調用的初衷。
3. 使用多個async函數
實際上,一個async函數內部包含的調用應該是強相關的,沒有依賴關系的函數調用不應該放在一個async函數中,分開來邏輯更清晰。
4. 並行執行的一些寫法
1. 不能再內部非async function中使用await
async function dbFuc(db) { let docs = [{}, {}, {}]; // 報錯,forEach的function是非async,不能使用await docs.forEach(function (doc) { await db.post(doc); }); } //這里不需要 async function dbFuc(db) { let docs = [{}, {}, {}]; // 可能得到錯誤結果,這樣調用也不能得到正確的結果 docs.forEach(async function (doc) { await db.post(doc); }); }
2. 循環調用await可以使用for循環或for of循環
//for of async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } } //map + Promise.all async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } //map + for of async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); } //for循環中去請求網頁,若await操作成功,會break退出;若失敗,會catch捕獲,進入下一輪循環 const superagent = require('superagent'); const NUM_RETRIES = 3; async function test() { let i; for (i = 0; i < NUM_RETRIES; ++i) { try { await superagent.get('http://google.com/this-throws-an-error'); break; } catch(err) {} } console.log(i); // 3 } test();
7. async的錯誤處理
使用try...catch進行包裹,例如:
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } }
如果僅僅對一部分錯誤進行處理或者忽略,可以局部的進行包裹,或者對單獨的promise進行catch,例如:
async function myFunction() { await somethingThatReturnsAPromise().catch((err)=> { console.log(err); }) } async function myFunction() { try{ await somethingThatReturnsAPromise(); } catch(e){} await somethingElse(); }
Promise的錯誤處理,推薦用async + await來寫:
// 存值 createData(title, successBack, errorBack) { // 使用key保存數據 storage.save({ key: title, data: 'true', }).then(successBack(), errorBack()); }
改寫為
//存值 async createData1(title, successBack, errorBack) { try { // 使用key保存數據 await storage.save({ key: title, data: 'true', }); successBack() } catch (e) { errorBack() } }
形式上更加清晰一些。
8. async函數的實現原理
async函數就是將執行器和Generator做為一個整體返回。
async function fn(){} //等同於 function fn(){ return spawn(function* (){ }) }
spawn的實現
function spawn(genF) { /**** * 返回的是一個promise */ return new Promise(function(resolve, reject) { var gen=genF(); //運行Generator這個方法; /*** * 執行下一步的方法 * @param fn 一個調用Generator方法的next方法 */ function step(fn) { //如果有錯誤,則直接返回,不執行下面的await try { var next=fn(); }catch (e){ return reject(e) } //如果下面沒有yield語句,即Generator的done是true if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then((val)=>{ step(function(){ return gen.next(val) } ) }).catch((e)=>{ step(function(){ return gen.throw(e) } ) }) } step(function () { return gen.next(); }) }); }
參考:https://segmentfault.com/a/1190000008677697
https://juejin.im/post/5b56837c6fb9a04fe0181555
https://www.cnblogs.com/goloving/p/8013119.html
https://blog.csdn.net/u011272795/article/details/80197481