這其實不是難懂的概念,但卻是非同步程式設計中最基礎的螺絲釘,我自己在學習這 JS 非同步領域這系列的順序是這樣排的:

Callback => Promise => Async/Await

確保自己了解之後,再往下一個階段邁進,對於認知非同步反而會顯得更加輕鬆,不然容易變成拆東牆補西牆。

這個概念是我從 huli 的JavaScript 中的同步與非同步(上):先成為 callback 大師吧!學習到的,也是這篇讓我意識到callback的重要性,推薦可以去看。那麼就開始進入今天的主題。

Callback Funtion定義

學習一個新的名詞術語,想知道它究竟是什麼,有很多方式,Callback來說的話我覺得可以看 MDN 文檔的定義,寫的蠻好理解:

A callback function is a function passed into another function as an argument

中文來說的話就是,當一個函數,作為參數傳遞給另一個函數,就是callback

舉例來說:
小白、小紅、小黃三個人在工作,可以這樣寫。

1
2
3
whiteWork();
redWork();
yellowWork();

雖然程式會確實的從上到下執行,所以看起來沒有問題,但卻不能保證執行的順序,但今天如果使用Callback就可以,像是我想要小白做完後小紅再開始工作,那我就可以把小紅放到小白的Callback裡面。

1
2
3
4
whiteWork(function () {
redWork();
});
yellowWork();

這樣子的好處,是讓程式的某個函數執行完,再接著執行其他的函式。

那換來看看 MDN 的例子:

1
2
3
4
5
6
7
8
9
10
function greeting(name) {
alert(`Hello, ${name}`);
}

function processUserInput(callback) {
const name = prompt("Please enter your name.");
callback(name);
}

processUserInput(greeting);

這段程式執行後會像是這個樣子。

  1. 跳出一個視窗詢問名字。

  2. 輸入名字(vic),按確定

  3. 跳出”Hello,vic”

正常沒有使用Callback的版本如下。

1
2
3
4
5
6
7
8
9
function greeting(name) {
alert(`Hello, ${name}`);
}

function processUserInput() {
const name = prompt("Please enter your name.");
}
processUserInput();
greeting();

但會發現跟上面不一樣,輸入名字後,卻找不到名字。

這個就是Callback很重要的地方,這種呼叫後順序不一致的狀態,我想要哪個先呼叫,有時候不是先放前面就沒事了,像是在這邊我雖然把processUserInput()放前面,但不代表後面的greeting()就會乖乖的等上面的問題問完才顯示,在JavaScript的世界裡面,會越快完成的事情會先做完,有一件要等待的事情,一件馬上顯示出來的事情時,不管排序都會先去做那馬上可以完成的,那要是有很多件不確定時間的事情,就會完全無法確認順序。

但是有了Callback就不一樣,可以藉由某個做完再呼叫的機制,確保順序,在這種單執行緒的環境下,JavaScript沒辦法像是其他程式語言可以直接多工去同時做很多事情,所以需要有一種「被呼叫才做事」的功能,這也是我理解為什麼需要一個函數,作為參數傳遞給另一個函數的原因。

其他比喻

在其他地方有看到一個用工人來解釋Callback Funtion很好懂的比喻,在JS20min Day — 18 關於回呼生活化 (Callback)看到的。

假如小白跟小黃都是工人。

小白是一個正在睡覺的工人,被叫時才會起來工作。
而小黃是勤奮型工人,他會去工作,也會去叫小白起來工作。

1
2
3
4
5
6
7
8
9
10
function whiteWork() {
console.log("被小黃叫,來去工作");
}

function yellowWork(call) {
console.log("先工作,叫小白");
call();
}

yellowWork(whiteWork);

這樣的執行順序會是: 1.工作,叫小白 2.被小黃叫,來去工作

就是當一個函數,作為參數傳遞給另一個函數,在這邊小白會作為一個參數去傳遞給小黃這個函數,而本身小白也是一個函數。

Callback Hell

假如事情一定要一個做完才接著下一個,後面的Callback會需要使用到前面Callback的結果,也就是必須在Callback中再Callback,那整體的架構就勢必沒辦法是平行的,會很複雜,像是這樣。


出處:https://github.com/explooosion/emoji-comment-collection/issues/6

就這是多層環環相扣後所產生的波動拳,不只看起來很是複雜,維護起來時也會很麻煩,這也是為什麼後面還需要使用到promise的原因,關於這部分後續幾天會提到,那今天的介紹就到這邊。

下一篇會講述在JavaScript中的非同步程式設計是怎麼一回事,明天見~

reference

[1] MDN - Callback function
[2] W3Schools - JavaScript Callbacks
[3] The Modern JavaScript Tutorial - Introduction: callbacks
[4] You Don’t Know JS - Callbacks
[5] 你懂 JavaScript 嗎?#23 Callback
[6] 重新認識 JavaScript: Day 18 Callback Function 與 IIFE