細說 async/await 相較於 Promise 的優勢

Micherwa發表於2019-01-13

前言

上一篇文章 「前端面試題系列1」今日頭條 面試題和思路解析 中提到了 async/await。它是在 ES8 中正式提出的,但是我發現,身邊的朋友用到 async/await 的並不多。那麼今天,我們就具體地聊聊什麼是 async 函式,與 Promise 相比較,有哪些寫法與運用上的優勢。

熟悉的同學,也可以溫故而知新,正文開始。。。

async 函式是什麼?

談及非同步回撥函式的巢狀,總會讓人感到煩躁,特別是當業務邏輯複雜,往往需要呼叫幾次 ajax 才能拿到所有需要的資料。

從最早的回撥函式,到 Promise 物件,再到 Generator 函式,每次都有所改進,但又讓人覺得不徹底。它們都有額外的複雜性,都需要理解抽象的底層執行機制。所以,我們需要一種方法,更優雅地解決非同步操作。於是,async函式出現了。

一句話解釋:async 函式,就是 Generator 函式的語法糖。

它有以下幾個特點:

  • 建立在 promise 之上。所以,不能把它和回撥函式搭配使用。但它會宣告一個非同步函式,並隱式地返回一個Promise。因此可以直接return變數,無需使用 Promise.resolve 進行轉換。
  • 和 promise 一樣,是非阻塞的。但不用寫 then 及其回撥函式,這減少程式碼行數,也避免了程式碼巢狀。而且,所有非同步呼叫,可以寫在同一個程式碼塊中,無需定義多餘的中間變數。
  • 它的最大價值在於,可以使非同步程式碼,在形式上,更接近於同步程式碼。
  • 它總是與 await 一起使用的。並且,await 只能在 async 函式體內。
  • await 是個運算子,用於組成表示式,它會阻塞後面的程式碼。如果等到的是 Promise 物件,則得到其 resolve 值。否則,會得到一個表示式的運算結果。

為何說 async 函式是語法糖

async 函式的實現,其實就是將 Generator 函式和自動執行器,包裝在一個函式裡。下面的這個例子,來自阮老師的 《async 函式的含義和用法》 一文。

async function fn(args) {
    // ...
}

// 等同於
function fn(args) {
    return spawn(function*() {
        // ...
    });
}

// spawn 函式就是自動執行器
function spawn(genF) {
    return new Promise(function(resolve, reject) {
        var gen = genF();
        function step(nextF) {
            try {
                var next = nextF();
            } catch(e) {
                return reject(e); 
        }
        if(next.done) {
            return resolve(next.value);
        } 
        Promise.resolve(next.value).then(function(v) {
            step(function() { return gen.next(v); });      
        }, function(e) {
            step(function() { return gen.throw(e); });
        });
    }
    step(function() { return gen.next(undefined); });
  });
}
複製程式碼

所以說,async 函式是 Generator 函式的語法糖。另外,它相對較新,屬於 ES8 中的語法。但是轉碼器 Babel 已經支援,轉碼後就能使用。

async 相較於 Promise 的優勢

1.相比於 Promise,它能更好地處理 then 鏈

function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
複製程式碼

現在用 Promise 方式來實現這三個步驟的處理。

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
        });
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
複製程式碼

如果用 async/await 來實現的話,會是這樣:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
}
doIt();
複製程式碼

結果和之前的 Promise 實現是一樣的,但是這個程式碼看起來是不是清晰得多,幾乎跟同步程式碼一樣。

2.中間值

現在把業務要求改一下,仍然是三個步驟,但每一個步驟都需要之前每個步驟的結果。Pomise的實現看著很暈,傳遞引數太過麻煩。

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
        });
}
doIt();
複製程式碼

用 async/await 來寫:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
}
doIt();
複製程式碼

沒有多餘的中間值,更加優雅地實現了。

3.除錯

相比於 Promise 更易於除錯。

因為沒有程式碼塊,所以不能在一個返回的箭頭函式中設定斷點。如果你在一個 .then 程式碼塊中使用偵錯程式的步進(step-over)功能,偵錯程式並不會進入後續的 .then 程式碼塊,因為偵錯程式只能跟蹤同步程式碼的每一步。

細說 async/await 相較於 Promise 的優勢

現在,如果使用 async/await,你就不必再使用箭頭函式。你可以對 await 語句執行步進操作,就好像他們都是普通的同步語句一樣。

細說 async/await 相較於 Promise 的優勢

總結

每一個特性的出現,總有它的用途,而只有用了,才能更好地理解它。

JavaScript的非同步編寫方式,從 回撥函式 到 Promise、Generator 再到 Async/Await。表面上只是寫法的變化,但本質上則是語言層的一次次抽象。讓我們可以用更簡單的方式實現同樣的功能,而不需要去考慮程式碼是如何執行的。

換句話說就是:非同步程式設計的最高境界,就是根本不用關心它是不是非同步

參考文獻

PS:歡迎關注我的公眾號 “超哥前端小棧”,交流更多的想法與技術。

細說 async/await 相較於 Promise 的優勢

相關文章