Promise 非同步處理


Posted by Calon on 2022-04-26

在了解 Promise 前首先來了解什麼是同步與非同步。

同步(Synchronous)、非同步(Asynchronous)

假設今天回到家會做開門、脫鞋子、拿起鞋子、進門、關門,以同步來講就是:

開門 → 脫鞋子 → 拿起鞋子 → 進門 → 關門

簡單來說就是前一個動作執行完才能執行下一個動作。那這時有人會說,可是今天我想先脫鞋拿起鞋子後再進門關門不行嗎?

當然可以,這種先做其他可以做的事,並且不會影響其他事情的就叫做非同步。

在 JavaScript 裡當遇到非同步事件時,會把它放到一個叫做事件佇列(Event Queue)的地方,等待同步事件處理完之後才會去處理非同步事件。


callback hell

在 Promise 還沒出現之前,當我們希望許多非同步的 function 能依照想要的順序執行時,就會在非同步 function 裡呼叫另一個非同步 function,當數量一多的時候,就會變成過度巢狀並且難以閱讀,也就是俗稱的 callback hell。


用 Google 搜尋 callback hell 常會出現的梗圖。
圖片來源

接下來就來了解 Promise 物件是怎麼改善 callback hell 的問題。


狀態和流程

在進入 Promise 同時就會進入 pending 等待事件完成,當事件完成後會依據結果回傳成功回應或是拒絕理由:

  • 當結果為成功時(fulfilled)會用 resolve 變數來傳送訊息。
  • 當結果為拒絕,操作失敗時(rejected)會用 reject 變數來傳送訊息。

而接收訊息的地方在 Promise 是用 then()catch() 來分別接收 resolvereject 訊息。

const toEatDinner = () => {
  const randomNum = Math.floor(Math.random() * 2);
  return new Promise((resolve, reject) => {
    if(randomNum){
        setTimeout(() => resolve('成功吃到晚餐(fulfilled)'), 5000);
    }else{
      reject('沒帶錢包,沒辦法吃晚餐(rejected)');
    };
  });
};

toEatDinner()
.then(res => console.log(res))  // 接收 resolve 的訊息
.catch(err => console.log(err)); // 接收 reject 的訊息


Promise.all、Promise.race

Promise.race

使用 Promise.race 不管事件成功與否都只會回傳第一個完成的事件的結果。

const toEatDinner = (status, food, time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() =>{
      if(status){
        resolve(`成功吃到 ${food}`);
      }else{
        reject(`沒錢了,沒辦法吃 ${food}`);
      };
    }, time);
  });
};

Promise.race([
    toEatDinner(1, 'chicken', 2000),
    toEatDinner(0, 'pizza', 1000),
    toEatDinner(1, 'rice', 3000)
  ])
  .then(res => console.log(res))
  .catch(err => console.log(err)); // 沒錢了,沒辦法吃 pizza


Promise.all

如果想要同時執行多個 Promise,可以使用 Promise.all(),但要注意的是當其中一個 Promise 為 reject 時,那麼全部的 Promise 都會視為失敗。

const toEatDinner = (food, time) => {
  const randomNum = Math.floor(Math.random() * 2);
  return new Promise((resolve, reject) => {
    if(randomNum){
      setTimeout(() => resolve(`成功吃到 ${food}`), time);
    }else{
      reject(`沒錢了,沒辦法吃 ${food}`);
    };
  });
};

Promise.all([
     toEatDinner('chicken', 3000),
     toEatDinner('pizza', 1000),
     toEatDinner('rice', 2000),
  ])
  .then(res => console.log(res))
  .catch(err => console.log(err));


Chain 鏈接方法

如果想要依序執行多個 Promise,可以善用 then()

const toEatDinner = (food, time) => {
  return new Promise(resolve => {
    if(true){
      setTimeout(() => resolve(`成功吃到 ${food}`), time);
    };
  });
};

toEatDinner('chicken', 3000)
  .then(res => {
  console.log(res);
  return toEatDinner('pizza', 1000)
}).then(res => {
  console.log(res);
  return toEatDinner('rice', 2000)
}).then(res => {
  console.log(res);
});


但需要注意的是,當其中有一個結果為失敗,Promise 會跳過中間的 then 找最近的 catch 來接收訊息。

以下面為例,當編號 1 的 Promise 執行完回傳成功訊息並執行編號 2 的 Promise,但當編號 2 的 Promise 執行結果失敗,就會繞過編號 4 的 Promise 跑到下面的 catch 接收錯誤訊息,並且直接執行編號 5 的 Promise。

當編號 2 執行結果失敗時的執行順序:1 → 2 → catch → 5

const toEatDinner = (status, food, time) => {
  return new Promise((resolve, reject) => {
    if(status){
      setTimeout(() => resolve(`成功吃到 ${food}`), time);
    }else{
      reject(`沒錢了,沒辦法吃 ${food}`);
    };
  });
};

toEatDinner(1, 'chicken', 3000)  // 1
  .then(res => {
  console.log(res);
  return toEatDinner(0, 'pizza', 1000)  // 2
}).then(res => {
  console.log(res);
  return toEatDinner(1, 'rice', 2000)  // 3
}).then(res => {
  console.log(res);
  return toEatDinner(1, 'sushi', 2000)  // 4
}).catch(err => {
  console.log(err);
  return toEatDinner(1, 'chips', 1000)  // 5
}).then(res => {
  console.log(res);
});

參考資料

#javascript #Promise







Related Posts

Day 27 - Tkinter & *args & **kwargs

Day 27 - Tkinter & *args & **kwargs

遞迴 費氏數列詳解

遞迴 費氏數列詳解

JAVA筆記_集合物件, 泛型, 序列化

JAVA筆記_集合物件, 泛型, 序列化


Comments