[料理佳餚] ES8(ES2017)神奇的 async/await 語法在 ES6(ES2015)怎麼呈現?

JavaScript ES8(ES2017)的版本支援 async/await 語法,就像這樣:

這個語法對於主力是 C# 的我們並不陌生,async/await 語法可以在該進行等候資源的時候進行等候,而且不會打亂我們閱讀程式碼的順序,清晰度大大提昇,不過我很好奇,它是怎麼辦到的?

爬了幾篇文章之後,了解到,說穿了其實 ES8 的 async/await 是 Generator Function + Promise 的語法糖,而這兩個東西是從 ES6(ES2017)加進來的,所以我們可以用 Generator Function + Promise 在 ES6 實現 async/await 的效果。

什麼是 Generator Function?

我們先看一下它的語法:

function* test() {
    let a = yield getA();

    console.log(a);

    let b = yield getB();

    console.log(b);
}

function* 即是將 Function 宣告為 Generator Function,用 yield 來暫停工作,將執行權交出去,進行等候,這是最關鍵的地方,而呼叫端則用 next() 方法來控制往下執行,像這樣:

let testFunc = test();

testFunc.next();
testFunc.next();

而且它這種特性,可以拿來做狀態機,更詳細的 Generator Function 介紹,可以參考這篇文章「Generator 函數的含義與用法」。

什麼是 Promise?

Promise 的出現解決了 Callback Hell 的麻煩,任何有延遲結果的 Function 都可以用 Promise 加以封裝,像 Fetch API 回傳的就是 Promise,底下是一個簡單的 Promise 範例:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(100);
    }, 500);
});

即使沒有延遲結果的 Function,如果為了統一介面,也可以用 Promise 封裝起來。

let promise = new Promise((resolve, reject) => {
    const sum = 50 + 50;

    resolve(sum);
});

然後接 then() 方法就能在 Promise 得到 resolve 或 reject 的結果之後,接續執行處理的邏輯。

promise.then(result => {
    console.log(result);
}, reason => {
    console.log(reason);
});

而且狀態是會保存起來的,即使在這之後的任意時間點呼叫 then() 方法,依舊能行。

setTimeout(() => {
    promise.then(result => {
        console.log(result);
    }, reason => {
        console.log(reason);
    });
}, 1000);

更詳細的 Promise 介紹,可以參考這篇文章「你懂 JavaScript 嗎?#24 Promise」。

改寫吧

所以文章開頭的那個範例,我們可以改寫成這樣:

function* sum(integers) {
    let _sum = 0;

    for (let i = 0; i < integers.length; i++) {
        let num = integers[i];

        if (num === 5) {
            let response = yield fetch("/Test/Data");
            let data = yield response.json();

            num = data.num;
        }

        _sum += num;
    }

    return Promise.resolve(_sum);
}

當我們確信 Generator Function 的 yield 個數時,就可以配合 yield 的個數呼叫固定次數的 next() 方法來執行。

const genSum = sum([1, 2, 3, 4, 5, 6, 7, 8, 9]);

genSum.next().value
    .then(response => genSum.next(response).value)
    .then(data => genSum.next(data).value)
    .then(result => {
        console.log(result);
    });

但是這樣寫其實沒有什麼彈性,邏輯一改就爆了,為此我們可以為 Generator Function 做一個執行器,讓它自動往下執行。

function genSumRunner(integers) {
    const genSum = sum(integers);

    function next(data) {
        const result = genSum.next(data);

        if (result.done) return result.value;

        return result.value.then(data => next(data));
    }

    return next();
}

genSumRunner([1, 2, 3, 4, 5, 6, 7, 8, 9]).then(result => {
    console.log(result);
});

以上,就給各位朋友做參考,希望大家都能在前端的世界活得好好的。

參考資料

C# 指南 ASP.NET 教學 ASP.NET MVC 指引
Azure SQL Database 教學 SQL Server 教學 Xamarin.Forms 教學