Web開發學習筆記15 — 呼叫堆疊、同步與非同步、Promise、Async/Await、Conditional ternary operator


Posted by Teagan Hsu on 2021-01-03

The Call Stack(呼叫堆疊)

我們今天有一個function,它需要好幾個函式一齊運作才能完成。而呼叫堆疊的作用就是幫Javascript跟蹤函數的位置,以及當函式完成任務後,讓值返回正確的位置。
像以下範例,要完成rightTriangle(3, 4, 5),就必須先知道rightTriangle()的計算方式,然後再把每個square()的值算出來。

function square(x){
    return x * x
}
function rightTriangle(a, b, c){
    return square(a) + square(b) === square(c)
}
rightTriangle(3, 4, 5)

以下是用Loupe網站展示出來的call stack:


Sync/Async(同步與非同步)

setTimeOut()的例子來說,我們今天想執行的順序是:
Good morning => Good afternoon => Good night

console.log('Good morning')
setTimeout(() => {
    console.log('Good afternoon')
}, 1000)
console.log('Good night')

但我們跑出的結果會是:

這是因為setTimeout()在1000毫秒後才被印出,所以順序會是:
Good morning => Good night => Good afternoon
這種狀況就是非同步(Async),browser接收到setTimeout()的需求後,不用等setTimeout()印出後才執行其他程式。
那如果我們想要按Good morning => Good afternoon => Good night的順序印出要怎麼做呢?
一般來說可以用callback,但當太多function被nested,就可能造成callback hell。還好現在有Promiseasync/await可以解決這個問題!下面會詳細講到。


Promise

Promise是ES6新增的物件,可以幫忙解決非同步(asynchronous)以及callback hell的問題:

簡單的promise寫法:

  • 運用then連結:如果正確完成第一個promise後,回傳一個新的promise,如果正確完成第二個promise後,回傳另一個新promise....
  • catch捕獲錯誤:只要有任一個promise拒絕,就會run寫在catch裡的指令。
function promise() {
    return new Promise((resolve, reject) => {
        resolve(value);
        reject(reason);
    })
}

promise()
    .then((value) => {
        //do somthing
        return promise()
    })
    .then((value) => {
        //do somthing
        return promise()
    })
    .catch(reason) {
        //do something when wrong}

Promise的狀態

總共有三種:

  • pending //執行中
  • fulfilled //已實現
  • rejecttion //已拒絕

Promise 方法

  • Promise.all(iterable)
    Promise.all方法是說,當所有promise都回傳resolve後,才進入下一階段的任務,只要有任一promise被reject就不會進入下一階段。iterable裡放陣列。
  • Promise.race(iterable)
    只要有任一個promise回傳resolve,就進入下一階段任務,其餘都忽略。如果全部都被reject則視為失敗。
  • Promise.resolve
    Promise.resolve(value)方法回傳一個以value參數判定結果的Promise物件。
  • Promise.reject
    Promise.reject(reason)方法返回一個Promise物件,該物件由於給定的原因而被拒絕。

Promise 範例

範例1:想要讓程式按照一定的順序執行
想要讓backgroundColor的顏色按照,紅、橙、黃、綠依次進行。

let colorChange = (color, time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            document.body.style.backgroundColor = color;
            resolve();
        }, time)
    })
}

colorChange('red', 1000)
    .then(() => colorChange('orange', 1000))
    .then(() => colorChange('yellow', 1000))
    .then(() => colorChange('green', 1000))

範例2:模擬請求資料的狀況
用setTimeout與random模擬連結資料的時間。

function request(url) {
    return new Promise((resolve, reject) => {
        let delay = Math.floor(Math.random() * 5000) + 3000;
        setTimeout(() => {
            if (delay < 5000) {
                resolve(`Here is your data from ${url}`);
            } else reject(`Connecting ERROR`);
        }, delay)
    })
}

request(`blog/api/page1`)
    .then((data) => {
        console.log(`blog/api/page1`)
        return request(`blog/api/page2`)
    })
    .then(() => {
        console.log(`blog/api/page2`)
        return request(`blog/api/page3`)
    })
    .then(() => {
        console.log(`blog/api/page3`)
        return request(`blog/api/page4`)
    })
    .then(() => {
        console.log(`blog/api/page4`)
    })
    .catch(() => {
        console.log(`A REQUEST ERROR!!!!`)
    })

可將程式碼貼到console測試結果看看。


async / await

async/await函式簡單來說就是promise的簡單寫法。

簡單的async/await寫法:

  • async加在前面function
  • await一定要寫在async function裡
  • async function跑完會回傳一個promise,後面可以接then繼續連結
async function doSomething() {  //創立async function
    await a(); //等a跑完,才執行b
    await b(); //b執行完畢
}
doSomething(); //呼叫上面的函式
doSomething().then(() => {})  //doSomething()跑完後,後面可以繼續接其他指令

像上述promise的範例1,使用async/await語法就會變成這樣:

async function rainbow() {
    await colorChange('red', 1000);
    await colorChange('yellow', 1000)
    await colorChange('orange', 1000)
    await colorChange('green', 1000)
}
rainbow();

如果想在跑完背景顏色後印出DONE!!,可以這麼寫:

//用then連結
rainbow().then(() => console.log(`DONE!`))

//或是再用一個async函式
async function rainbowDone() {
    await rainbow();
    console.log(`DONE!`)
}
rainbowDone();

處理 Error

那我們要如何抓取async函式中的Error呢?通常有兩種基本的方法:

  • 使用try...catch
  • 使用.catch
//使用try...catch
async function request() {
  try {
    let response = await fetch('http://blog/api/edit');
  } catch(err) {
    console.log(err); // TypeError: failed to fetch
  }
}
request(); 

//使用.catch
async function request() {
    let response = await fetch('http://blog/api/edit');
}
request().catch(console.log)  // TypeError: failed to fetch

用promise的範例2來重寫成async函式:

//使用try...catch抓取error
async function res(){
    try{ await request(`blog/api/page1`)
    await console.log(`blog/api/page1`)
    await request(`blog/api/page2`)
    await console.log(`blog/api/page2`)
    await request(`blog/api/page3`)
    await console.log(`blog/api/page3`)
    await request(`blog/api/page4`)}
   catch(err){
       console.log(err)
   }
}

//使用.catch抓取error
async function res(){
    await request(`blog/api/page1`)
    await console.log(`blog/api/page1`)
    await request(`blog/api/page2`)
    await console.log(`blog/api/page2`)
    await request(`blog/api/page3`)
    await console.log(`blog/api/page3`)
    await request(`blog/api/page4`)}
res().catch(console);

通常來說當我們使用async/await後,我們會改用常規的try..catch。但當我們不在async函數之內,就無法使用await語法,所以這時通常是添加.then/catch來處理結果或掉出來的error。


Conditional ternary operator

Conditional ternary operator又稱三元運算式,是透過:(冒號)?(問號)兩種運算子來簡化if語句使用。

語法

condition ? exprIfTrue : exprIfFalse

範例1:簡單示範
如果age大於等於18,則函式回傳Beer,反之則回傳Water

function beverage() {
    let age = Math.floor(Math.random() * 30) + 1;
    return (age >= 18) ? 'Beer' : 'Water';
}

範例2:if..else串接

function ticketPrice() {
    let age = Math.floor(Math.random() * 100) + 1;
    console.log(age);
    return (age <= 6) ? '$5' :
        (18 > age && age > 6) ? '$10' :
        (65 > age && age > 17) ? '$15' :
        '$5';
}

// 等同於:

function ticketPrice(){
    if (age <= 6) { return '$5'; }
    else if (18 > age && age > 6) { return '$10'; }
    else if (65 > age && age > 17) { return '$15'; }
    else { return '$5'; }
}

參考資料:

  1. Async/await
  2. Promise - MDN
  3. Javascript: Promises Explained With Simple Real Life Examples
  4. 你懂 JavaScript 嗎?#24 Promise
  5. How do I return the response from an asynchronous call?
  6. 使用 Promise
  7. 重新認識 JavaScript: Day 26 同步與非同步
  8. Conditional (ternary) operator - MDN

#call stack #Promise #Async #Sync #Conditional ternary operator







Related Posts

[進階 js 01] 變數的資料型態

[進階 js 01] 變數的資料型態

 [Leetcode in Java] 24. Swap Nodes in Pairs

[Leetcode in Java] 24. Swap Nodes in Pairs

[Day07] Monad

[Day07] Monad


Comments