[JS] Promise 的使用
const p = new Promise(callback<resolve, reject>)
// .then() 中可以放入兩個 callback,如果需要提早攔截並處理錯誤是可行的
p.then(<resolveHandler>, [<rejectHandler>])
.then(<resolveHandler2>, [<rejectHandler2>])
.catch(<rejectHandler>)
.finally(<finallyHandler>)
Promise.all('<array>')
Promise.race('<array>')
使用範例:
- 示範 Promise 和 Async/Await @ PJCHENder Gist
- Basic Promise @ JSFiddle
- Promise Chain: 在 callback 中回傳另一個 Promise @ JSFiddle
- Use new Promise directly in .then without putting in callback @ JSFiddle
- Error Handling in Promise @ JSFiddle
- Demo how to use Promise.all() 和 Promise.race() @ JSFiddle
範例:
const p = new Promise((resolve, reject) => {
setTimeout(function () {
resolve(3);
}, 1000);
});
/**
* 方法一:在 callback 中 return new Promise
**/
p.then((value) => {
return new Promise((resolve, reject) => {
console.log(value); // 3
setTimeout(function () {
resolve(value * 2);
}, 1000);
});
});
/**
* 方法二:直接代入會 return Promise 的 function
**/
p.then(promiseFn) // 6
.then((value) => {
console.log(value); // 12
})
.catch((err) => {
// catch any error in "then"
console.log(err);
});
function promiseFn(value) {
console.log(value); // 6
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve(value * 2);
}, 1000);
});
}
目錄
[TOC]
觀念
- Promise 有三種狀態:
pending
,resolved/fulfilled
,rejected
。 new Promise
內的函式會立即被執行,當resolve
得到內容後,才會執行.then
。
- 在
.then
的resolvedCallback
中,可以得到在new Promise
中 resolve 內所得到的值(value)。 - 如果在
.then
的resolvedCallback
中return
一個值,則這個值會以 Promise 物件的形式傳到下一個.then
。 - 因此在下一個
.then
的resolvedCallback
中,可以取得上一個.then
中 return 的值。 - 但如果我們在
.then
中是 return 另一個new Promise
,則下一個.then
會等到這個 Promise 中的 resolve 得到值後才執行。 - 且在下一個
.then
的resolvedCallback
中,可以得到上一個new Promise
中resolve
的值
基本使用
/**
* Promise 基本使用
* 在 new Promise 內的函式會被馬上執行,
* 當 resolve 得到內容後,才會執行 .then。
**/
const myPromise = new Promise((resolve, reject) => {
console.log('In new Promise, start callback'); // 立即執行
setTimeout(() => {
// 一秒後執行
let response = 10;
resolve(response);
}, 1000);
});
myPromise.then((value) => {
// 在 myPromise 被 resolve 時執行
console.log('The answer is ' + value);
});
myPromise.catch((error) => {
// 在 myPromise 被 reject 時執行
console.log('error', error);
});
Basic Promise @ JSFiddle
Promise 使用 .then 串接
/**
* Promise 使用 .then 串接
* 在 .then 裡面 resole(value) 的 value 一樣是 promise 物件,
* 可以被傳到下一個 .then 中使用。
**/
const myPromise = new Promise((resolve, reject) => {
console.log('In new Promise, start callback'); // 立即執行
setTimeout(() => {
let data = 10;
resolve(data); // 1 秒後執行
}, 1000);
});
// 在 .then 裡面在 return resolve 的 value
// 這個新的 value 會被傳到下一個 promise 的 resolver 內
myPromise
.then((value) => {
console.log('first .then'); // 被 resolve 後執行
return value + 3;
})
.then((value) => {
// 得到上一個 .then return 的值後執行
console.log('second .then');
console.log('The final value is ' + value);
});
我們可以透過 .then()
去 return 另一個 Promise,主要方法有兩種:
- 在 Promise 的
.then
callback 中去new
另一個 Promise - 直接在
.then
中透過 function 的方式new
Promise 而非在 callback 中執行
如果是在 Promise 的
.then
callback 中去new
另一個 Promise,要把這個 Promise 前面加上 return;如果是直接把它放在 .then 的 function 中,則不用在前面加上 return。
方法一:在 .then 的 callback 中 new 另一個 Promise
我們可以在一個 Promise 的 .then 中去 return 另一個 new Promise,:
.then((value) => {
return new Promise((resolve, reject) => {
resolve(value + 3)
})
})
// 這個 .then 會等到上面那個 Promise 被 resolved 後才執行
.then((value)=>{
console.log(value)
})
範例一
/**
* 在 Promise 中 new 另一個 Promise
**/
const myPromise = new Promise((resolve, reject) => {
// 立即執行
console.log('In first Promise, start callback');
setTimeout(() => {
let data = 10;
resolve(data); // 2 秒後執行
console.log('In fist Promise, resolve data');
}, 1500);
});
// 當前面 Promise 的 resolve 得到內容後,才會執行 .then
myPromise
.then((value) => {
// 在.then 的 resolvedCallback 中,可以得到在 new Promise 中 resolve 內所得到的值(value)。
console.log('In first then, the value is ' + value);
// 如果在 .then 的 resolvedCallback 中 return 一個值,則這個值會以 Promise 物件的形式傳到下一個 .then
return value + 3;
})
.then((value) => {
// 因此在這個 .then 的 resolvedCallback 中,可以取得上一個 .then 中 return 的值。
console.log('In second then, the value is ' + value);
console.log('In second Promise, start callback');
// 如果我們在 .then 中是 return 另一個 new Promise
return new Promise((resolve, reject) => {
setTimeout(() => {
// 2 秒後再執行
resolve(value + 3);
console.log('In second Promise, resolve data');
}, 1500);
});
})
// 則下一個 .then 會等到這個 Promise 中的 resolve 得到值後才執行。
.then((value) => {
// 在這個 .then 的 resolvedCallback 中,可以得到上一個 new Promise 中 resolve 的值
console.log('In third then, the value is ' + value);
})
.catch((reason) => {
console.log('Request Failed ' + reason);
});
output 的結果會分成兩個時間點:
--------- 立即執行
"In first Promise, start callback"
--------- 1.5 秒後執行
"In fist Promise, resolve data"
"In first then, the value is 10"
"In second then, the value is 13"
"In second Promise, start callback"
--------- 再 1.5 秒後執行
"In second Promise, resolve data"
"In third then, the value is 16"
範例二
/**
* EXAMPLE2: Return a new Promise in a Promise
**/
function waitASecond(seconds) {
console.log('start', seconds);
return new Promise((resolve, reject) => {
setTimeout(function () {
seconds++;
resolve(seconds);
}, 1000);
});
}
waitASecond(0)
.then((seconds) => {
console.log('In first .then', seconds);
return waitASecond(seconds);
})
.then((seconds) => {
console.log('In second .then', seconds);
return waitASecond(seconds);
})
.then((seconds) => {
console.log('In third .then', seconds);
});
Promise Chain: return a Promise in a Promise @ JSFiddle
方法二:在 .then 中代入會 return Promise 的函式
範例一
// EXAMPLE 1
function waitASecond(second) {
console.log(second);
return new Promise((resolve, reject) => {
setTimeout(function () {
second++;
resolve(second);
}, 1000);
});
}
waitASecond(0)
.then(waitASecond)
.then(waitASecond)
.then((second) => {
console.log(second);
});
範例二
// EXAMPLE2
const calculatePromise = new Promise((resolve, reject) => {
console.log('In new Promise, start callback');
setTimeout(() => {
let answer = 3 + 5;
resolve(answer);
}, 1000);
});
function addTwo(value) {
console.log('current value', value);
return value + 2;
}
function printResult(value) {
console.log('The final value is ' + value);
}
calculatePromise.then(addTwo).then(addTwo).then(printResult);
Use new Promise directly in .then without putting in callback @ JSFiddle
Promise 錯誤處理
可以在 Promise 的任何一個階段中加上 .catch(<err>)
做為錯誤處理,除非有特殊需要,不然 .catch()
通常會放在最後,在任何一個 .then()
的階段中有錯誤發生時,就會直接跳到最後的 .catch()
而不會繼續執行錯誤發生後的 .then()
:
/**
* Error Handling in Promise
**/
function waitASeconds(seconds) {
return new Promise((resolve, reject) => {
if (seconds > 3) {
reject('seconds is bigger then 3');
}
setTimeout(function () {
seconds++;
resolve(seconds);
}, 1000);
});
}
waitASeconds(0)
.then((seconds) => {
console.log('In first .then', seconds);
return waitASeconds(seconds);
})
.then((seconds) => {
console.log('In second .then', seconds);
return waitASeconds(seconds);
})
.then((seconds) => {
console.log('In third .then', seconds);
return waitASeconds(seconds);
})
.then((seconds) => {
console.log('In forth .then', seconds);
return waitASeconds(seconds);
})
.then((seconds) => {
console.log('In fifth .then', seconds);
return waitASeconds(seconds);
})
.catch((error) => {
console.warn(error);
});
Error Handling in Promise @ JSFiddle
無法被處理到的 Error(Unhandled Error)
在使用 Promise 時,有幾個需要特別留意的情況,當下面的情況發生時,這個 error 將無法被處理(handled),很有可能會看到 Uncaught (in promise)
或 Possibly unhandled rejection
。
建議閱讀:
- 實際上,Promise 裡有一個「隱含的 try...catch」(Implicit try...catch @ JavaScript.info)。
- try...catch:說明為什麼 try...catch 可能攔不到 error。
- Window: unhandledrejection event @ MDN
用 throw 和 reject 沒有差別的情況
JavaScript Promises - reject vs. throw @ StackOverflow
先來看一下這段程式:
function getPromise() {
const promise = new Promise((resolve, reject) => {
console.log('foo');
// 使用 throw 程式會終止在這
// throw new Error('reason');
// 使用 reject 的話程式會繼續往後執行
reject(new Error('reason'));
console.log('bar');
})
.then((data) => console.log('[getPromise]', data))
// throw 或 reject 都能被 catch 攔截
.catch((reason) => console.log('[getPromise]', reason));
}
一般來說,在 Promise 裡不論是用 throw
或 reject
會有類似但不完全相同的效果:
- 相同:都會被 catch 所攔截
- 如果是
reject
,它後面的程式會繼續被執行,所以會看到:- "foo", "bar", "[getPromise] Error: reason"
- 但如果是用
throw
,它後面的程式不會繼續執行,所以會看到的是:- "foo", "[getPromise] Error: reason"
Uncaught Error:在 Promise 中有其他 async function 且用了 throw
然而,如果這個 Promise 裡還有其他非同步的請求,像是這樣:
- 如果在 async function 中使用
throw
,則這個 Error 將無法再被 catch 所處理,這時候就會是Uncaught Error
- 如果在 async function 中使用
reject
,則這個 Error 仍然能改被 catch 所處理
try...catch
只能攔截到被包在 try{...}
中「當下執行」的程式,如果 try{...}
裡面執行的是非同步的程式(scheduled code),除非能夠在這裡 await
,否則攔截不到 (try...catch @ JavaScript.info)。
throw
是拋出錯誤;但 reject()
比較像是一個 callback function,因此,在非同步中使用 throw
的話,即使這個 Promise 有用 catch
也無法攔截到。
function getPromise(isSuccess = true) {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('foo');
// 如果在 Promise 中的非同步的函式直接 throw
// 它的錯誤將無法在 catch 被攔截
throw new Error('reason');
// 使用 reject 的話,Error 仍然可以在 catch 中被攔截
// reject(new Error('reason'));
console.log('bar');
}, 1000);
})
.then((data) => console.log('[getPromise]', data))
.catch((reason) => {
console.log('[reason] ', reason);
});
}
同理,因為在 try{...}
中是非同步的程式,且沒有使用 await
等它執行完,所以這個錯誤是不會被 try...catch
所攔截:
try {
setTimeout(function () {
noSuchVariable; // script will die here
}, 1000);
} catch (err) {
alert("won't work");
}
最外層的 function 不是 async function
這個情況和前者有點類似:
p
本身是一個 async function,它可以 catch 到sleep
這個 Promise 中 reject 的錯誤main
是一個一般的 function,它「沒辦法」在try...catch
中攔截到p
裡面的錯誤,而是需要使用p.catch()
async function p() {
try {
await sleep();
} catch (err) {
throw err;
}
}
function main() {
try {
p();
// 因為 main 不是 async function,這裡的 try...catch 是攔截不到 p() 裡面的 error 的
} catch (err) {
console.log(err);
}
}
Promise 沒有被 catch,且該 Promise 又沒有被 return
如果這個 Promise 本身沒有被 catch,且又沒有 return 讓其他地方處理,它會變成 Uncaught (in promise)
的錯誤:
只要這個 Promise 沒有被 catch,而且又沒有 return 自己,那麼即使把這個 Promise 用 try catch
包起來也沒用。
function getPromise(isSuccess = true) {
const promise = new Promise((resolve, reject) => {
throw new Error('reason');
}).then((data) => console.log('[getPromise]', data));
// 這個 Promise 本身沒有 catch 來處理 error
// .catch((reason) => console.log('[getPromise]', reason));
// 而且沒有 return 讓其他地方處理
// return promise;
}
解決方式
要避免這樣的情況:
- 記得每個 Promise 都要有
catch
- 「或」把 Promise return 讓其他地方可以處理這個 Error
如此,這個 Promise 就可以被使用 async...await
的 try...catch
或 Promise 本身的 catch
所攔截:
// 一旦 getPromise 有 return Promise,則
// 可以在 async...await 中用 try...catch 所攔截
async function main() {
try {
await getPromise(false);
// 或者使用 .catch() 亦可攔截錯誤
// .catch((reason: unknown) => {
// console.log('[main] promise catch ', reason);
// });
} catch (reason) {
console.log('[main] try catch ', reason);
}
}
進階使用
keywords: Promise.all(_Array_)
, Promise.race(_Array_)
這個建構式另外還提供兩種調用 promise 的方法 race() 與 all(): Promise.all(): 此方法可以同時執行大量 Promise 物件,並且在 “全部” 完成後回傳陣列。 Promise.race(): 此方法執行大量 Promise 物件,但僅會回傳最快回應的結果。
Promise.all([newPromise1, newPromise2, newPromise3, newPromise4])
.then((data) => {
// 一次性同時回傳成功訊息,回傳以上三個數值的陣列
console.log(data);
})
.catch((err) => {
// 失敗訊息 (立即)
console.log(err);
});
Promise.race([newPromise1, newPromise2, newPromise3])
.then((data) => {
// 僅會回傳一個最快完成的 resolve 或 reject
console.log('race', data);
})
.catch((err) => {
// 失敗訊息 (立即)
console.log(err);
});
此部分來自 JavaScript ES6 Promise @ 前端,沒有極限 by 卡斯伯
Demo how to use Promise.all() 和 Promise.race() @ JSFiddle
實際例子
利用 Promise 先透過 email 和 password 取得 access_key 和 secret 後,再用 access_key 和 secret 取得 token。
/**
* You can get token in a Promise
**/
const getTokenPromise = new Promise((resolve, reject) => {
request
.post(endpoint + '/users/cert')
.send({
email: '<your_email>',
password: '<your_password>',
})
.end((err, res) => {
resolve(res);
reject(err);
});
});
getTokenPromise
.then((response) => {
let parseResponse = JSON.parse(response.text);
console.log(parseResponse);
return new Promise((resolve, reject) => {
request
.post(endpoint + '/users/token')
.send({
access_key: parseResponse.access_key,
secret: parseResponse.secret,
})
.end((err, res) => {
resolve(res);
reject(err);
});
});
})
.then((response) => {
let parseResponse = JSON.parse(response.text);
// You can get Token Here
console.log(parseResponse);
})
.catch((err) => {
console.warn('getTokenPromise with error', err);
});
參考
- Using Promise @ MDN
- JavaScript ES6 Promise @ 前端,沒有極限 by 卡斯伯
- Accelerated ES6 JavaScript Training @ Udemy
- Write Better JavaScript with Promises @ DWB