Async/Await與Promise區別

 

自從Node的7.6版本,已經默認支持async/await特性了。如果你還沒有使用過他,或者對他的用法不太瞭解,這篇文章會告訴你爲什麼這個特性“不容錯過”。本文輔以大量實例,相信你能很輕鬆的看懂,並瞭解Javascript處理異步的一大殺器。

初識Async/await

對於還不瞭解Async/await特性的同學,下面一段是一個“速成”培訓。
Async/await 是Javascript編寫異步程序的新方法。以往的異步方法無外乎回調函數和Promise。但是Async/await建立於Promise之上。對於Javascript處理異步,是個老生常談卻歷久彌新的話題:

從最早的回調函數,到 Promise 對象,再到 Generator 函數,每次都有所改進,但又讓人覺得不徹底。它們都有額外的複雜性,都需要理解抽象的底層運行機制。
異步編程的最高境界,就是根本不用關心它是不是異步。

async 函數就是隧道盡頭的亮光,很多人認爲它是異步操作的終極解決方案。

Async/await語法

試想一下,我們有一個getJSON方法,該方法發送一個異步請求JSON數據,並返回一個promise對象。這個promise對象的resolve方法傳遞異步獲得的JSON數據。具體例子的使用如下:

1

2

3

4

5

6

7

8

const makeRequest = () =>

    getJSON()

        .then(data => {

            console.log(data)

            return "done"

        })

 

makeRequest()

  在使用async/await時,寫法如下:

1

2

3

4

5

6

const makeRequest = async () => {

    console.log(await getJSON())

    return "done"

}

 

makeRequest()

  

對比兩種寫法,針對第二種,我需要進一步說明:

1)第二種寫法(使用async/await),在主體函數之前使用了async關鍵字。在函數體內,使用了await關鍵字。當然await關鍵字只能出現在用async聲明的函數體內。該函數會隱式地返回一個Promise對象,函數體內的return值,將會作爲這個Promise對象resolve時的參數。
可以使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操作完成,再接着執行函數體內後面的語句。

2)示例中,await getJSON() 說明console.log的調用,會等到getJSON()返回的promise對象resolve之後觸發。

我們在看一個例子加強一下理解,該例子取自阮一峯大神的《ECMAScript 6 入門》一書:

1

2

3

4

5

6

7

8

9

10

11

12

function timeout(ms) {

    return new Promise((resolve) => {

        setTimeout(resolve, ms);

    });

}

 

async function asyncPrint(value, ms) {

    await timeout(ms);

    console.log(value);

}

 

asyncPrint('hello world', 50);

  上面代碼指定50毫秒以後,輸出hello world。

Async/await究竟好在哪裏?

那麼,同樣是處理異步操作,Async/await究竟好在哪裏呢?
我們總結出以下6點。

簡約而乾淨Concise and clean

我們看一下上面兩處代碼的代碼量,就可以直觀地看出使用Async/await對於代碼量的節省是很明顯的。對比Promise,我們不需要書寫.then,不需要新建一個匿名函數處理響應,也不需要再把數據賦值給一個我們其實並不需要的變量。同樣,我們避免了耦合的出現。這些看似很小的優勢其實是很直觀的,在下面的代碼示例中,將會更加放大。

錯誤處理Error handling

Async/await使得處理同步+異步錯誤成爲了現實。我們同樣使用try/catch結構,但是在promises的情況下,try/catch難以處理在JSON.parse過程中的問題,原因是這個錯誤發生在Promise內部。想要處理這種情況下的錯誤,我們只能再嵌套一層try/catch,就像這樣:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

const makeRequest = () => {

    try {

    getJSON()

        .then(result => {

            // this parse may fail

            const data = JSON.parse(result)

            console.log(data)

        })

        // uncomment this block to handle asynchronous errors

        // .catch((err) => {

        //   console.log(err)

        // })

        }

    catch (err) {

        console.log(err)

    }

}

  但是,如果用async/await處理,一切變得簡單,解析中的錯誤也能輕而易舉的解決:

1

2

3

4

5

6

7

8

9

10

const makeRequest = async () => {

     try {

         // this parse may fail

         const data = JSON.parse(await getJSON())

         console.log(data)

     }

     catch (err) {

         console.log(err)

     }

  }

  

條件判別Conditionals

想象一下這樣的業務需求:我們需要先拉取數據,然後根據得到的數據判斷是否輸出此數據,或者根據數據內容拉取更多的信息。如下:

 

const makeRequest = () => {
    return getJSON()
        .then(data => {
            if (data.needsAnotherRequest) {
                return makeAnotherRequest(data)
                        .then(moreData => {
                            console.log(moreData)
                            return moreData
                        })
            } 
            else {
                console.log(data)
                return data
            }
        })
}

 

這樣的代碼會讓我們看的頭疼。這這麼多層(6層)嵌套過程中,非常容易“丟失自我”。
使用async/await,我們就可以輕而易舉的寫出可讀性更高的代碼:

 

const makeRequest = async () => {
    const data = await getJSON()
    if (data.needsAnotherRequest) {
        const moreData = await makeAnotherRequest(data);
        console.log(moreData)
        return moreData
    } 
    else {
        console.log(data)
        return data    
    }
}

 

中間值Intermediate values

一個經常出現的場景是,我們先調起promise1,然後根據返回值,調用promise2,之後再根據這兩個Promises得值,調取promise3。使用Promise,我們不難實現:

 

const makeRequest = () => {
    return promise1()
        .then(value1 => {
            // do something
            return promise2(value1)
                .then(value2 => {
                    // do something          
                    return promise3(value1, value2)
                })
        })
}

 

如果你難以忍受這樣的代碼,我們可以優化我們的Promise,方案是使用Promise.all來避免很深的嵌套。
就像這樣:

 

 

const makeRequest = () => {
    return promise1()
        .then(value1 => {
            // do something
            return Promise.all([value1, promise2(value1)])
        })
        .then(([value1, value2]) => {
            // do something          
            return promise3(value1, value2)
        })
}

 

Promise.all這個方法犧牲了語義性,但是得到了更好的可讀性。
但是其實,把value1 & value2一起放到一個數組中,是很“蛋疼”的,某種意義上也是多餘的。

同樣的場景,使用async/await會非常簡單:

const makeRequest = async () => {
    const value1 = await promise1()
    const value2 = await promise2(value1)
    return promise3(value1, value2)
}

總結

Async/await是近些年來JavaScript最具革命性的新特性之一。他讓讀者意識到使用Promise存在的一些問題,並提供了自身來代替Promise的方案。
當然,對這個新特性也有一定的擔心,體現在:
他使得異步代碼變的不再明顯,我們好不容易已經學會並習慣了使用回調函數或者.then來處理異步。新的特性當然需要時間成本去學習和體會;
退回來說,熟悉C#語言的程序員一定會懂得這些學習成本是完全值得的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章