Callback

Callback(回調)是什麼

回調函式是指藉由將函式作為參數(argument)傳遞能通往另一個函式,也就是將控制權轉移到下一個函式中,類似於延續傳遞風格(Continuation-passing style, CPS); 而之對比的是直接風格(Direct style),其在於控制權移交較不明確,他是純粹回傳值,剩下的動作由下一行或其他函式執行。

//直接風格
function getAvatar(user){
	return user
}

function display(avatar){
	console.log(avatar)
}

const avatar = getAvatar('eddy')
display(avatar)
//CPS
function getAvatar(user, cb){
	cb(user)
}

function display(avatar){
    console.log(avatar)
}

getAvatar('eddy', display)

CPS優點

  • 程式碼精簡
  • 可搭配非同步函式來達到非阻塞式I/O
  • 可以提高函式的擴充性與重覆使用性

缺點

  • 在愈複雜的應用情況時,程式碼愈不易撰寫與組織,維護性與閱讀性較低
  • 在錯誤處理上較為困難

非同步回調函式

在前面的範例是以同步(Synchronous)的方式來執行callback,但大部分的callback都是用在非同步(Asynchronous)執行的,來達到不阻塞其他程式的進行。除了小部分的語言的API中使用的回調可能會使用同步方式之外,其他大部分都是使用非同步回調函式,如下幾種:

  • DOM事件
  • Ajax
  • 使用計時器(timer)函式: setTimeout, setInterval
  • 特殊的函式: nextTick, setImmediate
  • 執行I/O: 監聽網路、資料庫查詢或讀寫外部資源
  • 訂閱事件

非同步回調函式範例

function aFunc(value, callback){
	callback(value)
}

function bFunc(value, callback){
	setTimeout(callback, 0, value)
}

function cb1(value){ console.log(value) }
function cb2(value){ console.log(value) }
function cb3(value){ console.log(value) }
function cb4(value){ console.log(value) }

aFunc(1, cb1)
bFunc(2, cb2)
aFunc(3, cb3)
bFunc(4, cb4)

1

3

2

4

回調函式的寫法

明確定義回調函式名稱

function func(x, cb){
  cb(x)
}

function callback(value){
  console.log(value)
}

func(123456, callback)

使用匿名(anonymous)函式

function func(x, cb){
  cb(x)
}

func(123456, function(value){
  console.log(value)
})
  • callback函式名稱可以用匿名函式取代
  • callback函式傳入的value名稱可以自由定義
  • callback函式有Closure(閉包)結構的特性,可以獲取到func中的傳入參數,以及裡面的定義的值

回調地獄(Callback Hell)

當程式開發的越來越複雜時,可能會開始一個接著一個執行callback流程,進而形成了一個回調地獄(Callback Hell),使得程式較難維護、可讀性變差且難以追蹤問題,如下所示。

function getRecipe(){
    setTimeout(()=>{
    	const recipeID = [123, 839, 520, 752];
    	console.log(recipeID);
    	
        setTimeout(id => {
            const recipe = {title: 'Fresh tomato pasta',
                            publisher: 'Jay'};
            console.log(`${id}: ${recipe.title}`);
			
            setTimeout(publisher => {
                const recipe2 = {title: 'Italian Pizza',
                                 publisher: publisher};
                console.log(recipe2);
            }, 1500, recipe.publisher);
        }, 1500, recipeID[2]);
    }, 1500);
}

getRecipe();

[123, 839, 520, 752]

“520: Fresh tomato pasta”

[object Object] { publisher: “Jay”, title: “Italian Pizza” }

Ref