先舉一個例子,如果希望ABCDE這5個函數依次執行,我們可以寫出如下代碼。
在同步的情況下,這樣的代碼沒有任何問題。但如果ABCDE都是非同步,還需要按次序執行,這樣寫就不行了。通常我們會為非同步函數設定回調,當函數執行完的時候執行回調,例如
A(function(){ B(function(){ C(function(){ D(function(){ E(); }); }); });}); |
毫無疑問這樣的編程體驗是很差的。當非同步流複雜的時候回調嵌套層數會很多,完全就是一場噩夢。
這還不是最重要的,如果想表達“當AB都完成的時候執行C”這樣的流程,並且希望A/B可以並行,就不能簡單的用這樣的回調了。
雖然說“當AB都完成的時候執行C”可以通過設定一個布爾量來解決,但是“當ABCD都完成的時候執行E”這樣的邏輯就需要在每個函數執行完的時候去判斷其他函數是否執行完,雖然的確是可行的,但是編程體現是比較差的。
身為一名懶惰的程式員,這樣顯然滿足不了我們的胃口。
@樸靈 寫了一個EventProxy,提供了事件驅動的非同步編程體驗
var proxy = new EventProxy();proxy.assign('A', function(){ B(function(){ proxy.trigger('B'); });});proxy.assign('B', function(){ C(function(){ proxy.trigger('C'); });});proxy.assign('C', function(){ D(function(){ proxy.trigger('D'); });});proxy.assign('D', function(){ E();});A(function(){ proxy.trigger('A');});
可以看出通過訊息來驅動代碼可以讓非同步嵌套被“拉平”了,而如果要描述“當ABCD都完成的時候執行E”這樣的流程也很容易了
var proxy = new EventProxy();proxy.assign('A', 'B', 'C', 'D', E);A(function(){ proxy.trigger('A');});B(function(){ proxy.trigger('B');});C(function(){ proxy.trigger('C');});D(function(){ proxy.trigger('D');}); |
除了改善非同步編程體驗以外,EventProxy也可以提供一個自訂的事件系統。
EventProxy很簡單,原始碼只有300多行,但是對於我這樣的移動開發人員來說任何用不上的代碼都是負擔。
由於我自己將Event系統拆成了單獨的一個模組,而我(目前為止)也不需要EventProxy在trigger一個訊息的時候的參數傳遞的功能,對於some, any, not這些限定詞我也不需要,因此我自己實現了一個簡單版的非同步流量控制工具。
(function(export){var uid = 1;var Jas = function(){ this.map = {}; this.rmap = {};};var indexOf = Array.prototype.indexOf || function(obj){ for (var i=0, len=this.length; i<len; ++i){ if (this[i] === obj) return i; } return -1;};var fire = function(callback, thisObj){ setTimeout(function(){ callback.call(thisObj); }, 0);};Jas.prototype = { waitFor: function(resources, callback, thisObj){ var map = this.map, rmap = this.rmap; if (typeof resources === 'string') resources = [resources]; var id = (uid++).toString(16); // using hex map[id] = { waiting: resources.slice(0), // clone Array callback: callback, thisObj: thisObj }; for (var i=0, len=resources.length; i<len; ++i){ var res = resources[i], list = rmap[res] || (rmap[res] = []); list.push(id); } return this; }, trigger: function(resources){ if (!resources) return this; var map = this.map, rmap = this.rmap; if (typeof resources === 'string') resources = [resources]; for (var i=0, len=resources.length; i<len; ++i){ var res = resources[i]; if (typeof rmap[res] === 'undefined') continue; this._release(res, rmap[res]); // notify each callback waiting for this resource delete rmap[res]; // release this resource } return this; }, _release: function(res, list){ var map = this.map, rmap = this.rmap; for (var i=0, len=list.length; i<len; ++i){ var uid = list[i], mapItem = map[uid], waiting = mapItem.waiting, pos = indexOf.call(waiting, res); waiting.splice(pos, 1); // remove if (waiting.length === 0){ // no more depends fire(mapItem.callback, mapItem.thisObj); // fire the callback asynchronously delete map[uid]; } } }};export.Jas = Jas; // Jas is JavaScript Asynchronous (callings) Synchronizer})(window); |
使用起來也挺簡單
var flow = new Jas();flow.waitFor(['A', 'B'], function(){ // both A and B are done!!}); $.getJSON(url1, function(data){ // An ajax request flow.trigger('A');});$.getJSON(url2', function(data){ // Another ajax request flow.trigger('B');}); |
小結一下:
使用訊息驅動的方式可以讓我們在非同步編程中避免一些回調嵌套的噩夢,最佳化編程體驗,在流程有修改的時候也更加靈活,可以用一種接近“聲明”式的方式去描述非同步函數流。