前言

有時候很常見會有情況是需要很多的非同步操作,每一個非同步的後續操作都建立在前面的非同步操作的成功,所以會繼續上一步的結果,在使用callback處理這種狀況的時候,就會產生可怕的callback hell,而Promise Chain可以很好的改善這個問題,就讓我們看下去。

promise chain 的使用方式


從看一個例子來了解使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const promise = new Promise(function (resolve) {
resolve(1);
});

promise
.then(function (result) {
console.log(result); // 1
return result + 1;
})
.then(function (result) {
console.log(result); // 2
return result + 1;
})
.then(function (result) {
console.log(result); // 3
return result + 1;
})
.then(function (result) {
console.log(result); // 4
return result + 1;
});


這是使用了promise方式來處理的promise chain,藉由.then會回傳一個新的 promise,讓每一個在promise chainpromise都代表上一個非同步的步驟已經完成。

注意,return之後的東西是新的promise,所以上一個流程已經結束了,就像是有人等烤吐司機烤完吐司,才去啟動微波爐加熱便當,那麼當他已經走到微波爐準備加熱便當時,就代表烤土司機已經烤完吐司了,雖然這兩個步驟都需要等待,但是不會一起等是獨立的,上一個等完才換下一個等。

而在這個例子中,promise chain運行的流程如下:

  1. 初始 promise 會直接把 resolve 結果為 1。
  2. 然後 .then 開始作用,會創建一個新的 promise,以上一個的 return 作為值,在這裡是 1。
  3. 下一個.then 就會獲得上一個.then 的值(2),再進行處理之後,會將值傳遞給下一個處理階段。
  4. … 以此類推。

    這邊其實會比較沒有非同步的感覺沒有等待,可能用setTimeout的方式似乎比較好,但只要使用promise就一定會使用非同步的方式去進行的,內部的狀態還是會從pending未確認的狀態,變成Fulfilled,而每一次的.then就相當於重新判定一次。

    再次強調,這樣做之所以可行的核心是因為每一個 then()後都會回傳一個新的promise

    A 做完後,使用 then()會回傳新的 B,然後再用 B 去做事,做完後再用 then()回傳一個新的 C,這種一層一層傳遞下去,就像是一個鏈子一樣,所以會稱之為Promise Chain

    這裡會引申出一個新的問題,那就是如果把很多的 then()都寫在同一個promise那還叫做Promise Chain嗎?

    像是這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let promise = new Promise(function (resolve, reject) {
setTimeout(() => resolve(1), 500);
});

promise.then(function (result) {
console.log(result);
return result * 3;
});

promise.then(function (result) {
console.log(result);
return result * 5;
});

promise.then(function (result) {
console.log(result);
return result * 10;
});


這樣的話依舊是Promise,依然使用著非同步的方式,但其實就不是Promise Chain,它們沒有連再一起,就只是各自做各自的事情,互不關聯,沒有延續所以也就不會互相傳遞result,所以在這個例子來說,會發現全部印出來的東西都是 1。

我有想到了一個例子:


Promise Chain

  1. 如果吃完蘋果的話,休息五分鐘就去吃香蕉
  2. 如果吃完蘋果後跑去吃完香蕉,休息五分鐘就去吃西瓜
  3. 如果吃完蘋果後跑去吃完香蕉又去吃了西瓜,休息五分鐘就準備去廁所


    Promise
  4. 如果吃完蘋果的話,休息五分鐘就去吃香蕉
  5. 如果吃完蘋果的話,休息五分鐘就去吃西瓜
  6. 如果吃完蘋果的話,休息五分鐘去去廁所




回傳解決與拒絕

前面一直在用 then()來當作Promise Chain的下一個回傳,但其實連接的概念除了 then()之外還有 catch(),差別在於Promise有沒有被拒絕,當一個Promise被解決時,也就是狀態是fulfilled,那麼就會使用 then()返回已經解決的Promise,如果是被拒絕時,也就是狀態rejected,那麼就會使用 catch()來回傳,舉一個例子:

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
31
32
33
function promise(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (judge) {
resolve(`成功`);
} else {
reject("你失敗了謝謝");
}
}, 0);
});
}

promise(1) // 第一次成功,因為是true
.then((number) => {
console.log(number);
return promise(1); // 第二次成功,因為是true
})
.then((number) => {
console.log(number);
return promise(0); // 失敗,下面的就不會去看,會直接跳catch
})
.then((number) => {
console.log(number);
return promise(1);
})
.then((number) => {
console.log(number);
return promise(1234567890); // 不會有反應了
})
.catch((number) => {
console.log(number);
});

首先,要在自己寫的promise同時存在成功跟失敗的結果,要用成判斷式的型態,因為狀態是不可逆的,pending只要到了fulfilled或是rejected其中一個就再也回不去了。

然後這裡我寫的判斷是,要是值是 true 的時候就會成功,false 的話就會你失敗了謝謝,而正數的時候值會是 true,負數時值會是 false,下方的呼叫寫了三個 then()跟一個 catch(),這裡要測試的是當不管是幾個 then(),只要有一個條件不成立,值是 false,那麼就直接會跳到 catch()那裡去,所以會發現這個程式的結果是:

1
2
3
成功
成功
你失敗了謝謝


第一個 then()那裡回傳出去的值就已經是負數了,代表會是 false,所以後面的 then()都不用看,會直接到最後的 catch()結果,而如果想要再 catch()再開始繼續也是可以,就直接再傳一個新的promise結果即可。

1
2
3
4
5
6
7
.catch((r) => {
console.log(r);
return promise(1);
})
.then((r) => {
console.log(r); // 成功
});

總結

除了用then來串鏈形成Promise Chain的方式來處理很多個連續的非同步操作外,也有一種Promise的語法糖,叫做 async/await 的方式,因為原理還是Promise,所以是語法糖,關於這個以後會專門做一期文章給大家講解。

reference

[1] MDN - Using Promises
[2] Promises chaining