重構:從Promise到Async/Await

Fundebug發表於2019-02-23

摘要: 誇張點說,技術的發展與歷史一樣,順之者昌,逆之者亡。JS開發者們,趕緊擁抱Async/Await吧!

早在半年多之前,我就在鼓吹Async/Await替代Promise的6個理由,似乎還招致了一些批評。然而,直到最近,我才真正開始進行程式碼重構,拋棄Promise,全面使用Async/Await。因為,Node 8終於LTS了

Async/Await真的比Promise好嗎?

是的是的。

這些天,我大概重構了1000行程式碼,最大的感覺是程式碼簡潔了很多:

  • 真正地用同步的方式寫非同步程式碼
  • 不用寫then及其回撥函式,減少程式碼行數,也避免了程式碼巢狀
  • 所有非同步呼叫可以寫在同一個程式碼塊中,無需定義多餘的中間變數
  • async函式會隱式地返回一個Promise,因此可以直接return變數,無需使用Promise.resolve進行轉換

下面,我們可以通過一個非常簡單的示例來體驗一下Async/Await的酸爽:

示例1

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)

// 使用Promise
function usePromise()
{
    let a
    readFile("a.txt", "utf8")
        .then(tmp =>
        {
            a = tmp
            return readFile("b.txt", "utf8")
        })
        .then(b =>
        {
            let result = a + b
            console.log(result) // 輸出"Hello, Fundebug!"
        })

}

// 使用Async/Await
async function useAsyncAwait()
{
    let a = await readFile("a.txt", "utf8")
    let b = await readFile("b.txt", "utf8")
    let result = a + b
    console.log(result) // 輸出"Hello, Fundebug!"
}

usePromise()
useAsyncAwait()
複製程式碼

由示例可知,使用Async/Await極大地簡化了程式碼,使得程式碼可讀性提高了非常多。

Async/Await真的替代了Promise?

是的是的。

對於Async/Await替代Promise的6個理由,批評者執著於Async/Await是基於Promise實現的,因此替代這個詞不準確,這就有點尷尬了。

一方面,這裡替代的是非同步程式碼的編寫方式,並非完全拋棄大家心愛的Promise,地球人都知道Async/Await是基於Promise的,不用太傷心;另一方面,Promise是基於回撥函式實現的,那Promise也沒有替代回撥函式咯?

重構程式碼之後,我仍然用到了Promise庫bluebird。”Talk is cheap, Show me the code!”,大家不妨看看兩個示例。

示例2:Promise.promisify

使用Promise.promisify將不支援Promise的方法Promise化,呼叫非同步介面的時候有兩種方式:

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)

// 使用Promise
function usePromise()
{
    readFile("b.txt", "utf8")
        .then(b =>
        {
            console.log(b)
        })
}

// 使用Async/Await
async function useAsyncAwait()
{
    var b = await readFile("b.txt", "utf8")
    console.log(b) // 輸出"Fundebug!"
}

usePromise()
useAsyncAwait()
複製程式碼

Fundebug是全棧JavaScript錯誤監控平臺,支援各種前端和後端框架,可以幫助您第一時間發現BUG!

示例3:Promise.map

使用Promise.map讀取多個檔案的資料,呼叫非同步介面的時候有兩種方式:

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)
var files = ["a.txt", "b.txt"]

// 使用Promise
function usePromise()
{
    Promise.map(files, file =>
        {
            return readFile(file, "utf8")
        })
        .then(results =>
        {
            console.log(results)
        })
}

// 使用Async/Await
async function useAsyncAwait()
{
    var results = await Promise.map(files, file =>
    {
        return readFile(file, "utf8")
    })
    console.log(results)
}

usePromise()
useAsyncAwait()
複製程式碼

沒錯,我的確使用了Promise庫,readFile與Promise.map都是Promise函式。但是,在呼叫readFile與Promise.map函式時,使用Async/Await與使用Promise是兩種不同寫法,它們是相互替代的關係。

Async/Await有什麼問題嗎?

有啊有啊。

使用了await的函式定義時要加一個async,呼叫非同步函式的時候需要加一個await,這玩意寫多了也覺著煩,有時候還容易忘掉。不寫async程式碼直接報錯,不寫await程式碼執行會出錯。

示例4

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)

// 沒有Async
function withoutAsync()
{
    let b = await readFile("b.txt", "utf8") // 報錯"SyntaxError: Unexpected identifier"
    console.log(b) 
}

// 沒有await
async function withoutAwait()
{
    let b = readFile("b.txt", "utf8")
    console.log(b) // 列印"Promise..."
}

withoutAsync()
withoutAwait()
複製程式碼

既然Async/Await寫著有點添亂,可不可以不寫呢?我想以後應該是可以的,只要能夠自動識別非同步程式碼就行了,這應該也是未來的發展方向。至於說如何實現,那我就不知道了哎。

總結

JavaScript的非同步編寫方式,從回撥函式到Promise再到Async/Await,表面上只是寫法的變化,本質上則是語言層的一次次抽象,讓我們可以用更簡單的方式實現同樣的功能,而程式設計師不需要去考慮程式碼是如何執行的。在我看來,這樣的進步應該不會停止,有一天我們也許不用寫Async/Await了!

參考

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了7億+錯誤事件,得到了Google、360、金山軟體、百姓網等眾多知名使用者的認可。歡迎免費試用!

重構:從Promise到Async/Await

版權宣告

轉載時請註明作者Fundebug以及本文地址:
blog.fundebug.com/2017/12/13/…

相關文章