-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduction: callbacks #101
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,15 +1,15 @@ | ||||||
|
||||||
# Animated circle with callback | ||||||
# 使用回呼的動畫圈圈 | ||||||
|
||||||
In the task <info:task/animate-circle> an animated growing circle is shown. | ||||||
在作業 <info:task/animate-circle> 中,成長的圈圈以動畫呈現。 | ||||||
|
||||||
Now let's say we need not just a circle, but to show a message inside it. The message should appear *after* the animation is complete (the circle is fully grown), otherwise it would look ugly. | ||||||
現在,假設我們需要的不只是圈圈,還要在裡面顯示訊息。訊息應該要出現在動畫完成(圈圈完全地成長)*之後*,否則它看起來會很醜。 | ||||||
|
||||||
In the solution of the task, the function `showCircle(cx, cy, radius)` draws the circle, but gives no way to track when it's ready. | ||||||
在該項作業的解答中,函式 `showCircle(cx, cy, radius)` 畫了圈圈,但沒有提供追蹤它完成的方法。 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Add a callback argument: `showCircle(cx, cy, radius, callback)` to be called when the animation is complete. The `callback` should receive the circle `<div>` as an argument. | ||||||
增加一個回呼參數:`showCircle(cx, cy, radius, callback)` 讓它在動畫完成後被呼叫。 `callback` 應該要接收圈圈的 `<div>` 作為參數。 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Here's the example: | ||||||
範例如下: | ||||||
|
||||||
```js | ||||||
showCircle(150, 150, 100, div => { | ||||||
|
@@ -18,8 +18,8 @@ showCircle(150, 150, 100, div => { | |||||
}); | ||||||
``` | ||||||
|
||||||
Demo: | ||||||
範例: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[iframe src="solution" height=260] | ||||||
|
||||||
Take the solution of the task <info:task/animate-circle> as the base. | ||||||
將該作業的解答 <info:task/animate-circle> 當作基底。 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,68 +1,67 @@ | ||||||||||
|
||||||||||
|
||||||||||
# Introduction: callbacks | ||||||||||
# 介紹: 回呼 | ||||||||||
|
||||||||||
```warn header="We use browser methods in examples here" | ||||||||||
To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations. | ||||||||||
為了示範回呼、promise 及其他抽象概念的使用,我們將會使用一些瀏覽器的函式:更具體地說,載入腳本以及執行簡單的文件操作。 | ||||||||||
|
||||||||||
If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial. | ||||||||||
如果你並不熟悉這些方法,亦或是對於範例中的使用方式感到困惑,你可能會想要閱讀[下一部分](/document)教程中的一些章節。 | ||||||||||
|
||||||||||
Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise. | ||||||||||
我們會試著讓事情保持單純。不會有任何瀏覽器方面的複雜事物。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
``` | ||||||||||
|
||||||||||
Many functions are provided by JavaScript host environments that allow you to schedule *asynchronous* actions. In other words, actions that we initiate now, but they finish later. | ||||||||||
許多函式是由 JavaScript 的執行環境所提供,這些函式允許你安排*非同步*的動作。換句話說,我們現在啟動的動作,將在未來的某一刻完成。 | ||||||||||
|
||||||||||
For instance, one such function is the `setTimeout` function. | ||||||||||
舉例來說, `setTimeout` 就是一個這樣的函式。 | ||||||||||
|
||||||||||
There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters). | ||||||||||
真實世界中,還有其它非同步動作的例子。像是載入腳本及模組(我們會在後續的章節中介紹它們)。 | ||||||||||
|
||||||||||
Take a look at the function `loadScript(src)`, that loads a script with the given `src`: | ||||||||||
看看下面的 `loadScript(src)` 函式,它載入了指定 `src` 位置的腳本: | ||||||||||
|
||||||||||
```js | ||||||||||
function loadScript(src) { | ||||||||||
// creates a <script> tag and append it to the page | ||||||||||
// this causes the script with given src to start loading and run when complete | ||||||||||
// 建立一個 <script> 標記,然後將它加到頁面上 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
// 這會讓 src 位置的腳本,開始被載入,並且在載入完成後開始執行 | ||||||||||
let script = document.createElement('script'); | ||||||||||
script.src = src; | ||||||||||
document.head.append(script); | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
It appends to the document the new, dynamically created, tag `<script src="…">` with given `src`. The browser automatically starts loading it and executes when complete. | ||||||||||
這新的、動態生成的標記 `<script src="…">` 加到文件中。瀏覽器自動地開始載入它,並在載入完成後執行它。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
We can use this function like this: | ||||||||||
我們可以像這樣使用這個函式: | ||||||||||
|
||||||||||
```js | ||||||||||
// load and execute the script at the given path | ||||||||||
// 載入並執行給定路徑的腳本 | ||||||||||
loadScript('/my/script.js'); | ||||||||||
``` | ||||||||||
|
||||||||||
The script is executed "asynchronously", as it starts loading now, but runs later, when the function has already finished. | ||||||||||
腳本以〝非同步〞的方式被執行,因為它現在開始被載入,但之後才會執行。要注意的是在載入開始,到腳本執行之前,這個函式已經執行完畢了。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
If there's any code below `loadScript(…)`, it doesn't wait until the script loading finishes. | ||||||||||
如果有任何代碼在 `loadScript(…)` 下方,它不會等到腳本載入完成後才執行。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
```js | ||||||||||
loadScript('/my/script.js'); | ||||||||||
// the code below loadScript | ||||||||||
// doesn't wait for the script loading to finish | ||||||||||
// 在 loadScript 下方的代碼,不會等待腳本載入完成才執行 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
// ... | ||||||||||
``` | ||||||||||
|
||||||||||
Let's say we need to use the new script as soon as it loads. It declares new functions, and we want to run them. | ||||||||||
假設我們想要在腳本載入完成後,馬上使用它。腳本宣告了新的函式,而我們想要執行它們。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
But if we do that immediately after the `loadScript(…)` call, that wouldn't work: | ||||||||||
但是如果我們馬上在 `loadScript(…)` 呼叫後這麼做,是行不通的。: | ||||||||||
|
||||||||||
```js | ||||||||||
loadScript('/my/script.js'); // the script has "function newFunction() {…}" | ||||||||||
loadScript('/my/script.js'); // 腳本有 "function newFunction() {…}" | ||||||||||
|
||||||||||
*!* | ||||||||||
newFunction(); // no such function! | ||||||||||
newFunction(); // 沒有這個函式! | ||||||||||
*/!* | ||||||||||
``` | ||||||||||
|
||||||||||
Naturally, the browser probably didn't have time to load the script. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script. | ||||||||||
這是因為,瀏覽器沒有足夠的時間載入腳本。現在, `loadScript` 函式沒有提供某種方式來追蹤載入完成了沒。我們只能知道,腳本終究會載入,然後執行。但是我們想要在腳本載入完成後,使用來自該腳本的新函式及新變數。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads: | ||||||||||
讓我們新增一個 `callback` 函式到 `loadScript` 中,作為第二個參數使用,這個函式應當在腳本載入完成後執行。: | ||||||||||
|
||||||||||
```js | ||||||||||
function loadScript(src, *!*callback*/!*) { | ||||||||||
|
@@ -77,19 +76,19 @@ function loadScript(src, *!*callback*/!*) { | |||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
Now if we want to call new functions from the script, we should write that in the callback: | ||||||||||
現在,如果我們想要呼叫來自腳本的新函式,我們應該要將它寫在回呼當中: | ||||||||||
|
||||||||||
```js | ||||||||||
loadScript('/my/script.js', function() { | ||||||||||
// the callback runs after the script is loaded | ||||||||||
newFunction(); // so now it works | ||||||||||
// 回呼在腳本載入後執行 | ||||||||||
newFunction(); // 現在,它能運作了 | ||||||||||
... | ||||||||||
}); | ||||||||||
``` | ||||||||||
|
||||||||||
That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed. | ||||||||||
這就是它的概念:第二個參數是一個函式(通常是匿名的),它會在動作完成後被執行。 | ||||||||||
|
||||||||||
Here's a runnable example with a real script: | ||||||||||
這是一個實際腳本的可執行範例: | ||||||||||
|
||||||||||
```js run | ||||||||||
function loadScript(src, callback) { | ||||||||||
|
@@ -102,20 +101,20 @@ function loadScript(src, callback) { | |||||||||
*!* | ||||||||||
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { | ||||||||||
alert(`Cool, the script ${script.src} is loaded`); | ||||||||||
alert( _ ); // function declared in the loaded script | ||||||||||
alert( _ ); // 宣告在載入腳本的函式 | ||||||||||
}); | ||||||||||
*/!* | ||||||||||
``` | ||||||||||
|
||||||||||
That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete. | ||||||||||
這被稱為〝基於回呼〞風格的非同步編程。執行某些非同步動作的函式,應該要提供一個 `callback` 參數,讓我們能在非同步函式完成時,執行我們傳入的回呼。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Here we did it in `loadScript`, but of course it's a general approach. | ||||||||||
我們在 `loadScript` 中就是這樣做的,當然這是一個常見的方式。 | ||||||||||
|
||||||||||
## Callback in callback | ||||||||||
## 回呼中的回呼 | ||||||||||
|
||||||||||
How can we load two scripts sequentially: the first one, and then the second one after it? | ||||||||||
我們要如何循序地載入兩個腳本:先是第一個,然後是在他之後的第二個? | ||||||||||
|
||||||||||
The natural solution would be to put the second `loadScript` call inside the callback, like this: | ||||||||||
直覺的解決方式就是在回呼中放入第二個 `loadScript`,像這樣: | ||||||||||
|
||||||||||
```js | ||||||||||
loadScript('/my/script.js', function(script) { | ||||||||||
|
@@ -131,9 +130,9 @@ loadScript('/my/script.js', function(script) { | |||||||||
}); | ||||||||||
``` | ||||||||||
|
||||||||||
After the outer `loadScript` is complete, the callback initiates the inner one. | ||||||||||
在外層的 `loadScript` 完成後,回呼會啟動內層的函式。 | ||||||||||
|
||||||||||
What if we want one more script...? | ||||||||||
如果我們想在多一個腳本呢‧‧‧? | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
```js | ||||||||||
loadScript('/my/script.js', function(script) { | ||||||||||
|
@@ -142,7 +141,7 @@ loadScript('/my/script.js', function(script) { | |||||||||
|
||||||||||
*!* | ||||||||||
loadScript('/my/script3.js', function(script) { | ||||||||||
// ...continue after all scripts are loaded | ||||||||||
// ...在所有腳本載入後繼續執行 | ||||||||||
}); | ||||||||||
*/!* | ||||||||||
|
||||||||||
|
@@ -151,13 +150,13 @@ loadScript('/my/script.js', function(script) { | |||||||||
}); | ||||||||||
``` | ||||||||||
|
||||||||||
So, every new action is inside a callback. That's fine for few actions, but not good for many, so we'll see other variants soon. | ||||||||||
所以,每一個新動作都在一個回呼內。在只有少數動作的情況下,這是沒問題的。但如果有很多動作的話,那就不好了。我們很快會看到其它變型。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
## Handling errors | ||||||||||
## 錯誤處理 | ||||||||||
|
||||||||||
In the above examples we didn't consider errors. What if the script loading fails? Our callback should be able to react on that. | ||||||||||
上述的範例中,我們並沒有考量到錯誤。如果腳本載入失敗的話,該怎麼辦?我們的回呼應該要能夠對此作出應對。 | ||||||||||
|
||||||||||
Here's an improved version of `loadScript` that tracks loading errors: | ||||||||||
下面是 `loadScript` 的改良版本,追蹤了載入的錯誤: | ||||||||||
|
||||||||||
```js | ||||||||||
function loadScript(src, callback) { | ||||||||||
|
@@ -173,32 +172,32 @@ function loadScript(src, callback) { | |||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
It calls `callback(null, script)` for successful load and `callback(error)` otherwise. | ||||||||||
成功時它呼叫 `callback(null, script)`,而失敗時它呼叫 `callback(error)`。 | ||||||||||
|
||||||||||
The usage: | ||||||||||
使用如下: | ||||||||||
```js | ||||||||||
loadScript('/my/script.js', function(error, script) { | ||||||||||
if (error) { | ||||||||||
// handle error | ||||||||||
// 處理錯誤 | ||||||||||
} else { | ||||||||||
// script loaded successfully | ||||||||||
// 成功載入腳本 | ||||||||||
} | ||||||||||
}); | ||||||||||
``` | ||||||||||
|
||||||||||
Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style. | ||||||||||
再強調一次,我們使用在 `loadScript` 的處理方式,其實相當常見。它被稱為〝錯誤優先回呼〞的風格。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
The convention is: | ||||||||||
1. The first argument of the `callback` is reserved for an error if it occurs. Then `callback(err)` is called. | ||||||||||
2. The second argument (and the next ones if needed) are for the successful result. Then `callback(null, result1, result2…)` is called. | ||||||||||
慣例如下: | ||||||||||
1. `callback` 的第一個參數保留給錯誤,如果它有發生的話。然後呼叫 `callback(err)`。 | ||||||||||
2. 第二個參數(以及之後的其它參數)保留給成功的結果。然後呼叫 `callback(null, result1, result2…)`。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
So the single `callback` function is used both for reporting errors and passing back results. | ||||||||||
所以單一的 `callback` 函式,被用於錯誤回報以及回傳結果。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
## Pyramid of Doom | ||||||||||
## 金字塔的詛咒(Pyramid of Doom) | ||||||||||
|
||||||||||
From the first look, it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine. | ||||||||||
第一眼看來,對於非同步編程來說,上述的方式是可行的。而它確實也是可行的。對於一到二層的巢狀呼叫來說,看起來還不錯。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
But for multiple asynchronous actions that follow one after another we'll have code like this: | ||||||||||
但是對於多個非同步動作,一個接著一個,我們將會有像這樣的代碼: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
```js | ||||||||||
loadScript('1.js', function(error, script) { | ||||||||||
|
@@ -217,7 +216,7 @@ loadScript('1.js', function(error, script) { | |||||||||
handleError(error); | ||||||||||
} else { | ||||||||||
*!* | ||||||||||
// ...continue after all scripts are loaded (*) | ||||||||||
// ...於所有腳本載入後執行 (*) | ||||||||||
*/!* | ||||||||||
} | ||||||||||
}); | ||||||||||
|
@@ -228,14 +227,14 @@ loadScript('1.js', function(error, script) { | |||||||||
}); | ||||||||||
``` | ||||||||||
|
||||||||||
In the code above: | ||||||||||
1. We load `1.js`, then if there's no error. | ||||||||||
2. We load `2.js`, then if there's no error. | ||||||||||
3. We load `3.js`, then if there's no error -- do something else `(*)`. | ||||||||||
上述代碼中: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
1. 我們載入 `1.js`,然後如果沒有錯誤的話。 | ||||||||||
2. 我們載入 `2.js`,然後如果沒有錯誤的話。 | ||||||||||
3. 我們載入 `3.js`,然後如果沒有錯誤的話。 -- 做其它的事 `(*)`. | ||||||||||
|
||||||||||
As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have real code instead of `...` that may include more loops, conditional statements and so on. | ||||||||||
隨著呼叫的層次越多,代碼變得越來越深,同時也增加了維護的難度,尤其是實際的代碼中可能會有更多的迴圈、條件判斷等等。而不是像範例中的 `...` 。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
That's sometimes called "callback hell" or "pyramid of doom." | ||||||||||
這有時候被稱為〝回呼地獄(callback hell)〞或〝金字塔的詛咒〞。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
<!-- | ||||||||||
loadScript('1.js', function(error, script) { | ||||||||||
|
@@ -263,11 +262,11 @@ loadScript('1.js', function(error, script) { | |||||||||
|
||||||||||
![](callback-hell.svg) | ||||||||||
|
||||||||||
The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control. | ||||||||||
〝金字塔〞狀的巢狀呼叫,隨著每一個非同步動作向右成長。很快地失去控制。 | ||||||||||
|
||||||||||
So this way of coding isn't very good. | ||||||||||
因此這樣的編碼方式並不夠好。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
We can try to alleviate the problem by making every action a standalone function, like this: | ||||||||||
我們可以試著藉由將每一個動作獨立為一個函式來舒緩這個問題,像這樣: | ||||||||||
|
||||||||||
```js | ||||||||||
loadScript('1.js', step1); | ||||||||||
|
@@ -294,17 +293,17 @@ function step3(error, script) { | |||||||||
if (error) { | ||||||||||
handleError(error); | ||||||||||
} else { | ||||||||||
// ...continue after all scripts are loaded (*) | ||||||||||
// ...在所有腳本載入後執行 (*) | ||||||||||
} | ||||||||||
}; | ||||||||||
``` | ||||||||||
|
||||||||||
See? It does the same, and there's no deep nesting now because we made every action a separate top-level function. | ||||||||||
看到了嗎?它的功能相同,但它現在沒了過深的巢狀,因為我們將每個動作都做成獨立的全域函式。 | ||||||||||
|
||||||||||
It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that one needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump. | ||||||||||
這能運作,但代碼看起來像被撕破的草稿。它很難閱讀,而且你大概也注意到了,讀者需要在閱讀時,在片段間做視線的跳躍。這很不方便,尤其是當讀者並不熟悉這段代碼,而且不曉得視線要跳到哪裡。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Also, the functions named `step*` are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of namespace cluttering here. | ||||||||||
而且,命名為 `step*` 的函式,全都只使用一次,它們被創造出來,只為了避免〝金字塔的詛咒〞。沒有任何一個函式會在動作鍊外,再被重新使用。因此這種方式有一點汙染了命名空間。 | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
We'd like to have something better. | ||||||||||
我們想要更好的。 | ||||||||||
|
||||||||||
Luckily, there are other ways to avoid such pyramids. One of the best ways is to use "promises," described in the next chapter. | ||||||||||
幸運地,有其它方法能避免這樣的金字塔。其中一種最棒的方式,是使用〝promises〞,將在下一章節介紹。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.