1. 程式人生 > 實用技巧 >Javascript中async和await

Javascript中async和await

//函式宣告
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。

  • 沒有顯式return,相當於return Promise.resolve(undefined);

  • return非Promise的資料data,相當於return Promise.resolve(data);

  • 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函式自帶執行器,可以自動執行。

  1. 更好的語義

async和await分別表示非同步和等待,語義更加明確

  1. 適用性更強

Generator模組後面只能是Thunk函式或Promise物件,而await後面可以是Promise或基本資料型別(如:數字,字串,布林等)

  1. 返回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();
        })
    });
}