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