理解 async/await

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

  • 內置執行器。Generator 函數的執行必須依靠執行器,而 Aysnc 函數自帶執行器,調用方式跟普通函數的調用同樣github

  • 更好的語義async 和 await 相較於 * 和 yield 更加語義化npm

  • 更廣的適用性co 模塊約定,yield 命令後面只能是 Thunk 函數或 Promise對象。而 async 函數的 await 命令後面則能夠是 Promise 或者 原始類型的值(Number,string,boolean,但這時等同於同步操做)json

  • 返回值是 Promiseasync 函數返回值是 Promise 對象,比 Generator 函數返回的 Iterator 對象方便,能夠直接使用 then() 方法進行調用api

Async 與其餘異步操做的對比

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

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

Promise 方式異步

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

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

Generator 方式函數

/**
 * 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() 的方式去執行。fetch

async 方式

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

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

語法

async 函數返回一個 Promise 對象

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

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

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

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 來使用。
只須要設置 presets 爲 stage-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 函數了。

相關文章
相關標籤/搜索