好用的 async/await


ES7 提出的async 函數,終於讓 JavaScript 對於異步操做有了終極解決方案。No more callback hell。
async 函數是 Generator 函數的語法糖。使用 關鍵字 async 來表示,在函數內部使用 await 來表示異步。
想較於 Generator,Async 函數的改進在於下面四點:git

  • 內置執行器。Generator 函數的執行必須依靠執行器,而 Aysnc 函數自帶執行器,調用方式跟普通函數的調用同樣
  • 更好的語義asyncawait 相較於 *yield 更加語義化
  • 更廣的適用性co 模塊約定,yield 命令後面只能是 Thunk 函數或 Promise對象。而 async 函數的 await 命令後面則能夠是 Promise 或者 原始類型的值(Number,string,boolean,但這時等同於同步操做)
  • 返回值是 Promiseasync 函數返回值是 Promise 對象,比 Generator 函數返回的 Iterator 對象方便,能夠直接使用 then() 方法進行調用

Async 與其餘異步操做的對比

先定義一個 Fetch 方法用於獲取 github user 的信息:github

function fetchUser() { 
    return new Promise((resolve, reject) => {
        fetch('https://api.github.com/users/superman66')
        .then((data) => {
            resolve(data.json());
        }, (error) => {
            reject(error);
        })
    });
}
複製代碼

Promise 方式npm

/**
 * Promise 方式
 */
function getUserByPromise() {
    fetchUser()
        .then((data) => {
            console.log(data);
        }, (error) => {
            console.log(error);
        })
}
getUserByPromise();
複製代碼

Promise 的方式雖然解決了 callback hell,可是這種方式充滿了 Promise的 then() 方法,若是處理流程複雜的話,整段代碼將充滿 then。語義化不明顯,代碼流程不能很好的表示執行流程。
Generator 方式json

/**
 * Generator 方式
 */
function* fetchUserByGenerator() {
    const user = yield fetchUser();
    return user;
}

const g = fetchUserByGenerator();
const result = g.next().value;
result.then((v) => {
    console.log(v);
}, (error) => {
    console.log(error);
})
複製代碼

Generator 的方式解決了 Promise 的一些問題,流程更加直觀、語義化。可是 Generator 的問題在於,函數的執行須要依靠執行器,每次都須要經過 g.next() 的方式去執行。
async 方式api

/**
 * async 方式
 */
 async function getUserByAsync(){
     let user = await fetchUser();
     return user;
 }
getUserByAsync()
.then(v => console.log(v));
複製代碼

async 函數完美的解決了上面兩種方式的問題。流程清晰,直觀、語義明顯。操做異步流程就如同操做同步流程。同時 async 函數自帶執行器,執行的時候無需手動加載。bash

語法

async 函數返回一個 Promise 對象babel

async 函數內部 return 返回的值。會成爲 then 方法回調函數的參數。異步

async function  f() {
    return 'hello world'
};
f().then( (v) => console.log(v)) // hello world
複製代碼

若是 async 函數內部拋出異常,則會致使返回的 Promise 對象狀態變爲 reject 狀態。拋出的錯誤而會被 catch 方法回調函數接收到。async

async function e(){
    throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));
複製代碼

async 函數返回的 Promise 對象,必須等到內部全部的 await 命令的 Promise 對象執行完,纔會發生狀態改變函數

也就是說,只有當 async 函數內部的異步操做都執行完,纔會執行 then 方法的回調。

const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
    await delay(1000);
    await delay(2000);
    await delay(3000);
    return 'done';
}

f().then(v => console.log(v)); // 等待6s後才輸出 'done'
複製代碼

正常狀況下,await 命令後面跟着的是 Promise ,若是不是的話,也會被轉換成一個 當即 resolve 的 Promise
以下面這個例子:

async function  f() {
    return await 1
};
f().then( (v) => console.log(v)) // 1
複製代碼

若是返回的是 reject 的狀態,則會被 catch 方法捕獲。

Async 函數的錯誤處理

async 函數的語法不難,難在錯誤處理上。
先來看下面的例子:

let a;
async function f() {
    await Promise.reject('error');
    a = await 1; // 這段 await 並無執行
}
f().then(v => console.log(a));
複製代碼

如上面所示,當 async 函數中只要一個 await 出現 reject 狀態,則後面的 await 都不會被執行。
解決辦法:能夠添加 try/catch

// 正確的寫法
let a;
async function correct() {
    try {
        await Promise.reject('error')
    } catch (error) {
        console.log(error);
    }
    a = await 1;
    return a;
}

correct().then(v => console.log(a)); // 1
複製代碼

若是有多個 await 則能夠將其都放在 try/catch 中。

如何在項目中使用

依然是經過 babel 來使用。
只須要設置 presetsstage-3 便可。
安裝依賴:

npm install babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime
複製代碼

修改.babelrc:

"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]
複製代碼

這樣就能夠在項目中使用 async 函數了。

相關文章
相關標籤/搜索