Javascript Promises

紀錄幾個常見的 JS Promises!

Promise.all

Promise.all 一定要所有的 Promise 都被 resolved 才可以,不然會跳error,
但全部的Promise還是會完成執行

1
2
3
4
5
6
7
8
9
10
11
12
13
const p1 = Promise.resolve('p1');
const p2 = new Promise((resolve, reject) => {
setTimeout(() => reject('rejected p2'), 2000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve('p3'), 3000);
});
Promise.all([p1, p2, p3])
.then(console.log)
.catch(console.error) // error: 'rejected p2'

所以就會造成也許有些 Promise 其實是被 resolved
但卻因為其中一個 rejected 無法進行一般的結果處理,
而且收到error的時間點搞不好有些Promise還沒有執行完畢
這時候就可以考慮忽略error handling並改成以下寫法
(像是要關閉DB連線的時候,要等每一個Promise都真的處理完才可以關閉)

1
2
3
4
5
6
7
8
// p1, p2, p3不變
Promise.all([
p1.catch(err => err),
p2.catch(err => err),
p3.catch(err => err)
]).then(console.log)
.catch(console.error) // ["p1", "rejected p2", "p3"]

async / await

async / await 真是好東西,尤其是寫到一堆promise的時候快分不清楚誰是誰的時候(?)

不過要記住 async function 回傳的一定是一個 Promise
而且要用 await 的話一定要用 async function 包住,
不能中途冒出一個 const fulfilledPromise = await request("/some/promise")

1
2
3
4
5
6
7
8
9
10
11
12
async function greetFromAjaxRequest(name) {
try {
const greeting = await request("/hi/from/somewhere"); // request will return a promise
return `${greeting}, ${name}!`;
} catch (error) {
throw Error('Oops, something wrong!');
}
}
greetFromAjaxRequest("Trina")
.then(console.log)
.catch(console.error)

多個 await

如果有多個await放在一起,
它就會照著順序等到上一個promise resolved了以後,才會執行
所以可以拿來chaining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const moment = require('moment');
const p1 = () => Promise.resolve('p1');
const p2 = (p1) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(moment() + ' resolve p2', 'result from p1:', p1);
resolve('p2');
}, 5000);
});
const p3 = (p2) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(moment() + ' resolve p3', 'result from p2:', p2);
resolve('p3');
}, 2000);
});
async function multiAwait() {
try {
const rp1 = await p1(); // 起始時執行
console.log(moment() + ' rp1'); // 等到 p1 resolved 之後執行
const rp2 = await p2(rp1); // 等到 p1 resolved 之後執行
console.log(moment() + ' rp2'); // 等到 p1 & p2 resolved 之後執行
const rp3 = await p3(rp2); // 等到 p1 & p2 resolved 之後執行
console.log(moment() + ' rp3'); // 等到 p1 & p2 & p3 resolved 之後執行
return 'all promises resolved';
} catch (e) {
console.log('err', e);
}
}

執行

1
multiAwait().then(console.log);

會出以下結果:

1
2
3
4
5
6
1501754077891 rp1
1501754082894 resolve p2
1501754082895 rp2
1501754084895 resolve p3
1501754084896 rp3
all promises resolved

多個 await but 先跑 promise

下面的 p1, p2, p3 其實在起始的時候就已經執行了
(因為它不是return一個promise,而是直接 new 一個promise)
所以跟 promise chaining 沒有關係
但除了await以外的部分
還是會等到它自己之前的promise都resolved了之後才會執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const moment = require('moment');
const p1 = Promise.resolve('p1');
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(moment() + ' resolve p2');
resolve('p2');
}, 5000);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(moment() + ' resolve p3');
resolve('p3');
}, 2000);
});
async function multiAwait() {
try {
const rp1 = await p1; // 起始時執行
console.log(moment() + ' rp1'); // 等到 p1 resolved 之後執行
const rp2 = await p2; // 起始時執行
console.log(moment() + ' rp2'); // 等到 p1 & p2 resolved 之後執行
const rp3 = await p3; // 起始時執行
console.log(moment() + ' rp3'); // 等到 p1 & p2 & p3 resolved 之後執行
return 'all promises resolved';
} catch (e) {
console.log('err', e);
}
}

執行

1
multiAwait().then(console.log);

會出以下結果:

1
2
3
4
5
6
1501211897186 rp1
1501211899171 resolve p3 // after 2 secs
1501211902171 resolve p2 // after 5 secs
1501211902171 rp2
1501211902172 rp3 // p3 兩秒後就 resolved 了,但因為程式碼順序(?)的關係,要等到 p1, p2, p3 都 resolved 之後才會被執行
all promises resolved

Chaining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function promiseFactory(sec, index) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("promise " + index + " resolved in " + sec + "at" + Math.round(new Date().getTime() / 1000)), sec);
});
}
const myPromises = [
() => promiseFactory(1000, 1),
() => promiseFactory(1000, 2),
() => promiseFactory(1000, 3)
];
// not chaining (concurrent)
myPromises.forEach(promise => {
promise().then(console.log);
});
// not chaining (concurrent)
for (let promise of myPromises) {
promise().then(console.log);
}
// chaining (in order)
myPromises.reduce((resolved, promise) => {
return resolved.then(promise).then(console.log);
}, Promise.resolve('ok'));