iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
2
Modern Web

JavaScript基本功修煉系列 第 26

JavaScript基本功修練:Day26 - Promise的語法糖:async/await

除了Promise之外,還有async/await語法來處理非同步程式,它背後的操作原理與Promise是一樣的,所以也被稱為Promise的語法糖,它可以把Promise寫得更直覺和簡潔(但其實也是見人見智)。

這篇文篇會整理以下知識:

  • async函式、await
  • 比較Promiseasync/await寫法
  • 錯誤處理(then/catch或try...catch方法)
  • async/await抓遠端資料

Async函式

async要寫在function前面:

async function func() {
    return 10;
}

func() //Promise {<fulfilled>: 10}

async函式一定會回傳一個Promise物件,即使例子中的func不是一個Promise物件,它也會被包裝成Promise物件。

Await

await一定要寫在async函式裏,否則會報錯。所以寫await時,就要一併寫async

await工作還未完成,就不會跑後面的程式碼

async函式裏,一定會等await的工作完成,才繼續跑該await後面的程式。這樣的寫法使程式碼看起來更像同步執行(同步即是「做完一件事才接著做下一件事」)。

第一個例子:

async function func(){
    const p = await new Promise( (resolve,reject) => 
    window.setTimeout( () => resolve(100),1000) )
    console.log('跑完p才跑我') //跑完p才跑我
    console.log(p) //100
}

func()

以上例子可見,在async函裏const p = ...之後的程式碼,需要等const p = ...跑完才會被執行。

第二個例子:
setTimeout延遲計算,完成以下的事(要做完1才可做2)。

  1. 把兩個數相加,顯示結果
  2. 再把另外兩個數相加,顯示結果
//把num1和num2相加
function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            resolve( console.log(num1 + num2) );
        },1000)
    })
}

//以下看起來像同步執行的感覺
async function func(){
    //p1跑完,才跑p2
    const p1 = await add(10,20); //30
    const p2 = await add(30,40); //70
}

func()

比較Promise與async/await寫法

當我們要執行多次非同步程式時,async/await的寫法會更易閱讀。以下例子是先把數字1放到func裏乘以10,之後把每次回傳結果乘以10。我們用此例子來模擬執行多次非同步程式時的情況。

如果用async/await去寫:

function func(num){
    return new Promise( (resolve, reject) => {
        resolve(num * 10)
    })
}

async function results(){
    const result1 = await func(1);
    console.log(result1); //10
    const result2 = await func(result1);
    console.log(result2); //100
    const result3 = await func(result2);
    console.log(result3); //1000
}

results()

如果用Promise去寫:

function func(num){
    return new Promise( (resolve, reject) => {
        resolve( num * 10)
    })
}

func(1)
    .then( success => {
        console.log(success); //10
        return(func(success));
    })
    .then( success => {
        console.log(success); //100
        return(func(success));
    })
    .then( success => {
        console.log(success); //1000
    })

比較兩種寫法,async/await的寫法裏,因為用了await,讓人一看就知道這裏會處理一個非同步程式,而且要等它完成才會跑下面的程式。所以async/await的寫法會更易閱讀。

錯誤處理(Error Handling)

catch處理錯誤情況

如果前一個await出錯,那麼後面的程式就不會跑了:

function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            reject( num1 + num2 ); //在這裏改成reject,模擬出錯
        },1000)
    })
}

async function func(){
    const p1 = await add(10,20); 
    const p2 = await add(30,40);
    return `第一次相加結果:${p1},第二次相加結果:${p2}` //Uncaught (in promise) 30
} 

func()

這時候async函式會報錯:Uncaught (in promise) 30

剛才提及過,async本身會回傳Promise物件,所以我們可以像平時一樣,用thencatch方法來抽取Promise物件裏的失敗/成功值。這裏我們用catch來處理錯誤情況:

function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            reject( num1 + num2 ); //在這裏改成reject,模擬出錯
        },1000)
    })
}

async function func(){
    const p1 = await add(10,20); 
    const p2 = await add(30,40);
    return `第一次相加結果:${p1},第二次相加結果:${p2}`
} 

func()
    .then( success => {
        console.log(`成功!`,success)
    })
    .catch( error => {
        console.log(`錯誤!`,`最後相加結果:${error}`)  //錯誤! 最後相加結果:30
    })

try...catch處理錯誤情況

除了thencatch這個組合,另一個方法是用trycatch的語法去處理。直接在async函式裏寫就可以了:

function add(num1,num2){
    return new Promise( (resolve, reject) => {
        window.setTimeout( () => {
            reject( num1 + num2 ); //在這裏改成reject,模擬出錯
        },1000)
    })
}

async function func(){
    try{
    const p1 = await add(10,20); 
    const p2 = await add(30,40);
    console.log(`第一次相加結果:${p1},第二次相加結果:${p2}`)
    
    } catch(error){
        console.log(`錯誤!`,`最後相加結果:${error}`) //錯誤! 最後相加結果:30
    }
} 

func()

try...catch用法

題外話,try...catch是一個針對錯誤處理的語法,原理跟Promise裏的thencatch一樣,只要出現錯誤,就會跳去跑catch裏的程式。catch(...)裏的err的名稱是可以自訂的。當try裏面的程式出現錯誤,JavaScript會把錯誤原因放到catch(...)裏的参數中,即是err

try {
  // code...
} catch (err) { // err是try區塊裏程式出錯的原因
  // error handling
}

另外,catch後面的参數可以不寫,這樣就不會回傳錯誤原因給你。例如以下範例(参考自這裏),因為在JSON.parse(JSONdata)出錯,所以會執行catch裏的程式:

let JSONdata = {'錯誤JSON格式': 123123};

try{
    //JSONdata的格式不合乎JSON的格式,所以JSON.parse會出錯
    let data = JSON.parse(JSONdata);
    console.log(data);
}catch{
    console.log('JSON資料格式錯誤!') //'JSON資料格式錯誤!'
}

async/await抓遠端資料

最常處理非同步程式就是抓遠端資料的情況了。以下例子用async/awaitXMLHttpRequest來抓遠端資料,並且用try...catch來處理成功或錯誤結果:

let getJSON = url => {
    return new Promise( (resolve,reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('get',url);
        xhr.send(null);

        xhr.onload = () => {
            if(xhr.status === 200){
                resolve(JSON.parse(xhr.responseText));
            }else{
                reject(new Error(xhr.statusText));
            }
        }
    })
}

//直接在async函式裏,用try...catch處理成功或失敗的結果
async function printJSON(url){
    try{
    const data = await getJSON(url);
    console.log(data); //顯示回傳資料  {results: Array(1), info: {…}}

    }catch(error){
        console.log('錯誤!',error);
    }
}

printJSON('https://randomuser.me/api/')

總結

  • async/awaitPromise的語法糖,背後運作原理與Promise是一樣,作用是把Promise寫得更易讀
  • async要寫在function前面
  • async函式一定會回傳一個Promise物件
  • await要放在async函式裏面
  • async/await的錯誤處理方法:
    • catch方法(就如平時寫Promise一樣)
    • try...catch方法

参考資料

JS 原力覺醒 Day16 - Async / Await:Promise 語法糖
JAVASCRIPT.INFO - Async/await
鐵人賽:JavaScript Await 與 Async
告別 JavaScript 的 Promise!迎接 Async/Await 的到來
JAVASCRIPT.INFO - Error handling, "try..catch"


上一篇
JavaScript基本功修練:Day25 - Promise
下一篇
JavaScript基本功修練:Day27 - AJAX基本概念
系列文
JavaScript基本功修煉31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言