iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
Modern Web

JavaScript學習日記系列 第 16

JavaScript學習日記 : Day16 - Promise

  • 分享至 

  • xImage
  •  

因為JavaScript屬於同步的語言,一次只能作一件事情,遇到非同步的事件就會把該事件挪到最後執行。

console.log("Start!");

setTimeout(() => { console.log("非同步事件") },1000)

console.log("End!")

// 

Start!
End!
非同步事件

Ajax也一樣屬非同步行為,如果要確保擷取到遠端資料後,並且針對遠端資料作處理的話,那了解promise的運作就非常重要。以下例子為Promise base的Ajax函式庫axios為例子:

let data = [];

axios.get('https://randomuser.me/api/').then((res) => {
    data = res
    console.log(data) // random user data
})

console.log(res) // []

Promise object的構造器語法如下:

let promise = new Promise(function(resolve, reject) {
  // executor
});

傳遞給new Promise的函數被稱為executor。當new Promise被創建,executor會自動運行。
當executor獲得了結果,會執行以下兩個callback:

  • resolve(value) ---如果任務完成並帶有結果value
  • reject(error) ---如果出現了error,error即為error object

new Promise構造器返回的promise對象具有以下的內部屬性:

  • state --- 最初是pending,然後在resolve被調用時變為fulfilled,或者在reject被調用時變為reject
  • result ---最初是undefined,然後在resolve(value)被調用時變為value,或者reject(error)被調用時變為error。

舉一個promise構造器和一個簡單的executor函數:

let promise = new Promise(function(resolve, reject) {
  // 當new promise時,自動執行此函數

  // 1秒後執行成功,帶有value "done"
  setTimeout(() => resolve("done"), 1000);
});

經過一秒的處理後,executor調用resolve('done')來產生結果,改變promise object的狀態:

reject的例子:

let promise = new Promise(function(resolve, reject) {
  // 1 秒后发出工作已经被完成的信号,并带有 error
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

executor只能調用一個resolve或是一個reject。所以再對resolve或是reject的調用都會被忽略。

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // 被忽略
  setTimeout(() => resolve("…")); // 被忽略
});

盡量以Error object調用reject

Resolve/reject可以立即進行(無非同步)
實際上executor通常是進行某些非同步操作,並在一段時間後調用resolve/reject,但這不是必須的

let promise = new Promise(function(resolve, reject) {
  // 不花时间去做这项工作
  resolve(123); // 立即给出结果:123
});

state與result都是內部屬性,無法直接獲取,但我們可以使用.then/.catch/.finally等方法。

then

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

.then接收兩個函數,第一個函數接收promise resolve後的結果,第二個則接收promise reject的結果。

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(
  result => alert(result), // 1秒後顯示 "done!"
  error => alert(error) // 不運行
);

reject的情況下:

let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

promise.then(
  result => alert(result), // 不運行
  error => alert(error) // 1秒後顯示 "Error: Whoops!"
);

如果只對成功完成感興趣,那也可以只提供一個函數。

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // 1秒後顯示 "done!"

catch

如果只對error感興趣,可以使用null作為第一個參數,.then(null,errorHandleFunction),或者使用.catch:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});

// .catch(f)跟promise.then(null, f)是一樣的
promise.catch(alert); // 1秒後顯示 "Error: Whoops!"

finally

finally中的callback function是在promise執行resolve或reject後執行,是一個很好執行清理(clean up)或是處理程序(handler),例如無論執行的結果如何都可以在finally中把loading indicator關掉。

new Promise((resolve, reject) => {
  // 做一些處理後調用resolve or reject
})
  // promise為settled,不管成功與否
  .finally(() => stop loading indicator)
  // 所以在finally中的處理,都會在我們處理成功/錯誤結果之前
  .then(result => show result, err => show error)
  1. finally的handler並沒有參數,所以在finally中我們並不知道promise是否成功
  2. finally處理結果會跟promise的處理結果一起傳給then
new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Promise ready"))
  .then(result => alert(result));

或是一起傳給catch

new Promise((resolve, reject) => {
  throw new Error("error");
})
  .finally(() => alert("Promise ready"))
  .catch(err => alert(err)); 

Promise chain

可以在.then()中回傳一個promise,那下一個.then()就會等到回傳的promise調用resolve或reject:

new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000)
}).then((result) => {
    console.log(result);
    return new Promise((resolve, reject)=> {
        setTimeout(() => resolve(2), 2000)
    })
}).then((result) => {
    console.log(result);
})

Error handle

通常會在Promise chain中的末端利用.catch()進行錯誤處理,所以可能會在多個.then()後才出現,如果整個promise chain都沒有報錯,那.catch()就不會被執行。

  • 隱式 try ... catch

直接看一個簡單的例子:

function testError() {
    return new Promise((resolve, reject) => {
        nonExistMethod();
    })
}

testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })

// error catch successfully! ReferenceError: nonExistMethod is not defined

為什麼沒有執行reject,error也會被.catch捕抓到呢? 原因是因為Promise內部有一層try..catch所包住,而且catch的部分預設使用reject function。

那如果error是出現在Promise外面呢?

function testError() {
    nonExistMethod();
    
    return new Promise((resolve, reject) => {
        resolve("result!")    
    })
}

testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) })

這樣的情況下,promise chain的.catch是沒有辦法捕捉到error的,必須自己利用try..catch來補捉:

function testError() {
    nonExistMethod();
    
    return new Promise((resolve, reject) => {
        resolve("result!")    
    })
}

try {
   testError().then((result) => { console.log(result) }).catch((error) => { console.log(`error catch successfully! ${error}`) }) 
} catch(error) {
    console.log("handle by outer try-catch")
}

Promise methods

最後介紹Promise的其他方法:

  • Promise.all --- 多個promise同時執行,全部完成後統一回傳
  • Promise.race --- 多個Promise同時執行,只回傳第一個完成的
  • Promise.resolve, Promise.reject --- 用來定義Fulfilled或Rejected的Promise物件

說明這些方法前,先定義一個promise函數,可以傳入兩個參數:

function ownPromise(count, time = 1000) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            count? resolve(`第${count}次成功!`) : reject("失敗")
        },time)
    })
}
  1. Promise.all

以數組的形式傳入多個promise函數,結果一樣以數組的形式回傳。

Promise.all([ownPromise(1), ownPromise(2), ownPromise(3,4000)]).then((res) => { console.log(res) })

// ["第1次成功!", "第2次成功!", "第3次成功!"]
  1. Promise.race

以數組的形式傳入多個promise函數,與Promise.all不同的是只會回傳單一結果,也就是第一個完成的。

Promise.race([ownPromise(1), ownPromise(2), ownPromise(3,4000)]).then((res) => { console.log(res) })

// 第1次成功!
  1. Promise.reject, Promise.resolve

直接看例子會比較快明白:

let result = Promise.resolve("result");

result.then(res => { console.log(res) }) // result

let rejectResult = Promise.reject("error");

rejectResult.catch(rej => { console.log(rej) }) // error

上一篇
JavaScript學習日記 : Day15 - 原型與繼承(二)
下一篇
JavaScript學習日記 : Day17 - Async Await
系列文
JavaScript學習日記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言