避免寫出巢狀的 Callback Hell

實務上有些 API 的呼叫有順序性,如必須先呼叫 產品 API,再根據產品資料呼叫 價格 API,像這類有相依性的 API,由於 Axios 回傳的 Promise 是非同步,直覺很容易寫成巢狀,這又回到 Callback Hell 的老路子,比較好的寫法是善用 Promise 特性,無論 API 相依幾層,都只有 then() 一層。

Version


Vue 2.5.17
Axios 0.18.0

Scenario


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
34
35
36
37
38
<template>
<div>
{{ product.title }} : {{ price }}
</div>
</template>

<script>
import getProducts from '../api/products';
import getPrice from '../api/price';

/**--------------------------------------------------------------
| data
*--------------------------------------------------------------*/

const data = function() {
return {
product: {},
price: 0,
};
};

/**--------------------------------------------------------------
| hook
*--------------------------------------------------------------*/

const mounted = function() {
getProducts()
.then(res => {
this.product = res.data[0];
getPrice(this.product.id)
.then(res => this.price = res.data.price);
});
};

export default {
name: 'HelloWorld',
data,
mounted,
};
</script>

24 行

1
2
3
4
5
6
7
8
const mounted = function() {
getProducts()
.then(res => {
this.product = res.data[0];
getPrice(this.product.id)
.then(res => this.price = res.data.price);
});
};

先由 getProducts() 呼叫 產品 API,再將 產品 ID 傳入 getPrice() 呼叫 價格 API ,由於非同步特性,很容易寫成這類 Callback Hell 式的巢狀寫法,若相依的 API 更多層,則巢狀問題將更加嚴重,將導致程式碼的可讀性變差,將來會很難維護。

Promise


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
34
35
36
<template>
<div>
{{ product.title }} : {{ price }}
</div>
</template>

<script>
import getProducts from '../api/products';
import getPrice from '../api/price';

/**--------------------------------------------------------------
| data
*--------------------------------------------------------------*/

const data = function() {
return {
product: {},
price: 0,
};
};

/**--------------------------------------------------------------
| hook
*--------------------------------------------------------------*/

const mounted = function() {
getProducts()
.then(res => this.product = res.data[0])
.then(res => getPrice(res.id))
.then(res => this.price = res.data.price);
};

export default {
name: 'HelloWorld',
data,
mounted,
};
</script>

24 行

1
2
3
4
5
6
const mounted = function() {
getProducts()
.then(res => this.product = res.data[0])
.then(res => getPrice(res.id))
.then(res => this.price = res.data.price);
};

既然 Axios 回傳為 Promise,就該善用 Promisethen(),除了將 res 資料寫入 Vue data 外,也順便回傳 Promise,因此可以繼續用 then() 呼叫下一個 API,如此不管相依 API 有幾層,都會只有 then() 一層,這才是 Promise 設計的初衷。

Conclusion


  • 既然 Axios 回傳為 Promise,就必須善用 Promise 的特性,才能避免 Callback Hell

Sample Code


完整的範例可以在我的 GitHub 上找到

2018-12-08