Skip to main content

Promise

11/9/2023 發布

先前我們在非同步網路請求有提到 Promise 物件,
這是在網路請求中會拿到的回傳值,我們可以用後綴 then 或 catch 來解析值,
那麼實際上它是怎麼運作的?

宣告物件

我們只能透過 then / catch 取值是因為 Promise 的格式被固定住了,
它設計的目標就是不能讓我們隨便取值。

在建立 Promise 物件時,是透過 class 物件的方式去新增出實體的,
所以需要給它一個參數當作初始值,它規定我們要給的參數會是一個函式。
並且這個函式又包含兩個參數:

  • 第一個參數為 resolve,為判定成功時會執行的動作。
  • 第二個參數為 reject,為判定失敗時會執行的動作。

這兩個參數也是函式,可以傳純值或是表達式(就是要跑出一個值給它用使用就對了),
所以寫起來會像是 resolve("成功"),代表 Promise 判定為成功時會回傳 "成功" 這個字串。

聽起來很抽象吧,我們可以搭配程式碼重現上面講的步驟:

const p = new Promise((resolve, reject) => {
resolve("成功");
reject("失敗");
});

p.then((res) => console.log(res)).catch((err) => console.log(err));

// 只會印出"成功"

為什麼只會印出成功呢?!

原因是我們沒有給出任何判斷的方式,所以最先被寫入的 resolve("成功") 就會直接執行,
反過來說我們先寫 reject("失敗") 一樣也不會印出成功,類似 early return 的概念。

因此通常我們在建立 Promise 物件的時候會另外呼叫函式或透過函式表達式
建立出一個空間進行判斷,再回傳建立好的 Promise 物件~

重新改寫一下剛剛的程式,回傳一個值判斷該數字是不是正數:

const numPromise = (value) =>
new Promise((resolve, reject) => {
if (typeof value === "number" && value > 0) {
resolve("正數");
}
reject("負數或不合法的值");
});

numPromise(3)
.then((res) => console.log(res))
.catch((err) => console.log(err));

numPromise(-1)
.then((res) => console.log(res))
.catch((err) => console.log(err));

numPromise("你好")
.then((res) => console.log(res))
.catch((err) => console.log(err));

// "正數"
// "負數或不合法的值"
// "負數或不合法的值"

現在我們可以判斷變數來決定要 resolve 還是 reject 了!


狀態變化

如果我們直接使用 console.log 去查看 Promise 物件的值,
有可能會出現三種狀態:

- Promise { pending }
- Promise { fulfilled }
- Promise { rejected }

pending 在直接 console.log 查看網路請求的結果時,
通常最容易得到這個結果,它代表 Promise 物件的內部還沒有進行任何 resolve 或 reject 的行為。

fulfilled 代表請求成功,rejected 當然就代表請求失敗了~

我們直接查看剛剛的 Promise:

console.log(numPromise(3)); // Promise {<fulfilled>: '正數'}
console.log(numPromise("你好")); // Promise {<rejected>: '負數或不合法的值'}

稍微改寫一下,加上 setTimeout 讓 Promise 內部的行為延遲處理:

const numPromise = (value) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof value === "number" && value > 0) {
resolve("正數");
} else {
reject("負數或不合法的值");
}
}, 300);
});

console.log(numPromise(3)); // Promise {<pending>}

這時候就會出現 pending 狀態了~
所以一如往常,我們必須去 then 或 catch 裡面取得結果。


async / await

非同步函式 async 聽起來好像很艱深,其實它就是 then / catch 的變體哦!
原本我們在進行網路請求時,存取結果都必須在 then / catch 裡面,
有時候結構上不是那麼好閱讀,這時候用 async / await 改寫,
就是很不錯的選擇,因為它的結構提高了閱讀性,
看到 async 的人也能馬上去判斷這個函式有可能是處理在網路請求。

唯一要注意的是,async / await 是作用在 Promise 物件的語法,
所以對一般函式是沒有用的,並不會有等待效果~

async 會前綴在函式定義最前面,表示該函式的回傳值會變成一個 Promise 物件,
而 await 必須在 async 函式下才能執行,await 就等同於 then 的作用,
只是取值的方式會變得不太一樣!

我們再改寫剛剛的程式碼,這時錯誤捕捉就要改 try / catch 的方式:

// 這個函式不變
const numPromise = (value) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof value === "number" && value > 0) {
resolve("正數");
} else {
reject("負數或不合法的值");
}
}, 300);
});

// 新增一個 async 函式來取代 then 取值
async function getPromiseResult(num) {
try {
const result = await numPromise(num);
console.log("請求成功", result);
} catch (err) {
console.log("請求錯誤", err);
}
}

getPromiseResult(3); // 請求成功 正數
getPromiseResult("你好"); // 請求失敗 負數或不合法的值
//

現在我們改用非同步函式 getPromiseResult 來呼叫 numPromise,
並且透過 await 來取值,resolve 與 reject 的結果都能正確捕捉到了!

有時候我們發起網路請求後不需要進行取值,
但仍然需要等待它執行完成才進行下面的程式時,
這時直接加上 await 就可以,而不用另外宣告變數來儲存值。

async function deleteItem() {
try {
// 不需要取值時就不用宣告變數儲存
await axios.delete(url, config);
renderList();
} catch (err) {
console.error(err);
}
}

參考資料