Continuation
先來看這段異步回調的例子
//A
setTimeout(function() {
//C
}, 1000);
//B
其中 A B表示程序的前半部分(也就是現在),而C則表示程序的後半部分(也就是將來部分)
A,B先執行,等待1s後執行C部分
換句話說,回調函數包裹或者說封裝了程序的(continuation)
回調的缺點
順序的大腦
我們的思考方式是一步一步的,但是從同步轉換到異步之後,可用的工具(回調)卻不是按照一步一步的方式來表達的
我們的順序阻塞式的大腦計劃行爲無法很好的映射到面向回調的異步代碼,對於他們在代碼中表達異步的方式,我們的大腦需要努力才能跟得上(例如多層嵌套的回調)
信任問題
再來看一個把回調當做continuation(也就是後半部分)的例子
//A
$.ajax({url: "..",success: function() {
//C
}});
//B
//A //B發生於現在,在JavaScript主程序的直接控制下
而//C會延遲到將來發生,並且是在第三方的控制下(本例中是函數ajax(..))
我們把這稱爲控制反轉,也就是把自己程序的一部分交給某個第三方
而在你的代碼和第三方工具(一組你希望有人維護的東西)之間並沒有明確表達的契約
那麼,你可能會遇到下面這些問題
- 調用回調過早
- 調用回調過完
- 調用回調次數太多或太少
- 沒有把所需要的環境/參數成功的傳給你的回調函數
- 吞掉可能出現的錯誤或異常
- ... ...
挽救回調
回調設計存在一些變體,試圖解決上面提到的部分信任問題
回調分離
爲了更優雅的處理錯誤,有些API設計提供了分離回調(一個用於成功通知,一個用於出錯通知)
$.ajax({
url: "..",
success: function() {
},
error: function() {
}
});
這種設計下,API的出錯處理函數常常是可選的,如果沒有提供的話,就是假定這個錯誤可以吞掉(ES6中的promise就是使用了這種分離回調設計)
error-first風格(Node風格)
還有一種常見的回調模式叫做"error-first風格"(有時也稱爲"Node風格",因爲幾乎所有的Node.js API都採用這種風格),其中回調的第一個參數保留用作錯誤對象(如果有的話)
如果成功的話,這個參數就會被清空/置假(後續的參數就是成功數據)
如果產生了錯誤結果,那麼第一個參數就會被置起/置真(通常就不會傳遞其他結果)
首先,這兩種風格並沒有解決主要的信任問題,比如重複調用回調的問題
而且這種模式可複用性不高,這意味着我們得一遍又一遍的給每個回調添加這樣的代碼