除了Promise之外,還有async/await語法來處理非同步程式,它背後的操作原理與Promise是一樣的,所以也被稱為Promise的語法糖,它可以把Promise寫得更直覺和簡潔(但其實也是見人見智)。
這篇文篇會整理以下知識:
async函式、await
Promise與async/await寫法async/await抓遠端資料async要寫在function前面:
async function func() {
    return 10;
}
func() //Promise {<fulfilled>: 10}
async函式一定會回傳一個Promise物件,即使例子中的func不是一個Promise物件,它也會被包裝成Promise物件。
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)。
//把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()
當我們要執行多次非同步程式時,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的寫法會更易閱讀。
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物件,所以我們可以像平時一樣,用then和catch方法來抽取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處理錯誤情況除了then和catch這個組合,另一個方法是用try和catch的語法去處理。直接在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裏的then和catch一樣,只要出現錯誤,就會跳去跑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/await和XMLHttpRequest來抓遠端資料,並且用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/await是Promise的語法糖,背後運作原理與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"