出處:你所必須掌握的三種異步編程方法callbacks,listeners,promisejavascript
coder都知道,javascript語言運行環境是單線程的,這意味着任何兩行代碼都不能同時運行。多任務同時進行時,實質上造成了一個隊列,當隊列中前一個事件結束時,才執行下一個事件。 若是隊列中任何一個事務費時太長,則會形成瀏覽器假死,阻塞其餘事務正常進行,影響用戶體驗。html
js中將任務執行分爲同步模式和異步模式,上面一種即爲同步模式,任何比較花時間的代碼最好設計成異步模式。經過異步編程方式,便可以達到僞多進程。常見的異步模式主要有callbaks,listeners(一種觀察者模式),promise,下面我將會詳細描述三者各自的優劣。前端
這是再常見不過的異步編程方式了,任何前端都寫過這樣的代碼吧:html5
function callback(){ console.log('do callbacks'); }; setTimeout(callback,300);
經過這種方式,你能夠簡單的實現異步編程。這裏須要注意,這裏的300ms只是理想時間,實際中各瀏覽器略有差別,主要取決於線程中其餘任務所用的時間。這個不具體討論,其中還涉及requestAnimationFrame等。本身能夠google。java
此方法缺點是什麼想必我不說你也知道,如:node
function fn1(f){
alert(1);
f();
};
function fn2(f){
alert(2);
f();
};
function fn3(){
alert(3);jquery
};git
fn1(function(){ fn2(fn3); });程序員
若是邏輯在複雜一點呢?恐怖看代碼的人會一頭霧水吧。而promise則能夠把這種複雜的嵌套寫的一看就明白,下面會講到。es6
這也是一種常見的異步編程方式,如:
$elem.on('click',doClick); function doClick(){ //do something };
設計模式中將這種方式稱之爲命令模式。這種方式的好處是你能夠註冊多個事件,待事件觸發時調用註冊的函數。
固然這裏不只僅是這種簡單的監聽方式,我須要將的是一種更抽象的異步編程方式——listeners。經過觀察者模式,咱們用一個數組來保存事件名稱以及事件綁定的函數方法,當須要觸發該事件時,咱們從數組中取出以前的註冊的函數。而EventProxy就是這種方式實現的,經過on,all註冊,emit觸發。如:
var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) { _.template(template, data, l10n); }); $.get("template", function (template) { // something ep.emit("template", template); }); $.get("data", function (data) { // something ep.emit("data", data); }); $.get("l10n", function (l10n) { // something ep.emit("l10n", l10n); });
下面我用本身的代碼簡單的實現這種方式
var Observer = { _callbacks:{}, register:function(name,fn,context){ if(!name){ return; } fn = fn || function(){}; if(!Observer._callbacks[name]){ var list = Observer._callbacks[name] = []; } list.push({fn:fn,context:context}); }, trigger:function(name){ var lists = []; if(!name){ //觸發全部 for(var key in Observer._callbacks){ lists.concat(Observer._callbacks[key]); } }else{ //觸發指定 lists = Observer._callbacks[name]; } for(var i = 0,len =lists.length;i<len;i++){ var list = lists[i]; var _fn = list.fn; return _fn.apply(list.context || this,[].slice.call(arguments,1)); } } }; Observer.register('fetch',function(param){},this); $.ajax(url,function(res){ Observer.trigger(name,res); });
這種模式在於跨模塊異步編程中顯得尤其重要。具體的實現邏輯剛纔已經講過,就不具體展開,看代碼註釋便可。
最後一種方法隆重登場,也是如今討論最多的異步編程方式。
代碼實際上是現實的概括總結並以另一種方式展示,咱們有時候看不懂,是由於寫代碼方式跟現實有差別。假如代碼邏輯這麼寫:
img1.callThisIfLoadedOrWhenLoaded(function() { // 加載完成 }).orIfFailedCallThis(function() { // 加載失敗 }); // 以及…… whenAllTheseHaveLoaded([img1, img2]).callThis(function(){ // 所有加載完成 }).orIfSomeFailedCallThis(function(){ // 一個或多個加載失敗 });
我相信非程序員也能看懂吧。這就是咱們要介紹的Promise異步編程方法。現有一些插件庫Q.js, when.js ,wind.js ,rsvp.js都是相似實現的,遵照一個通用的、標準化的規範:Promises/A+,jQuery 有個相似的方法叫 Deferred,但不兼容 Promises/A+ 規範,因而會有點小問題,使用需謹慎。jQuery 還有一個 Promise 類型,它實際上是 Deferred 的縮減版,因此也有一樣問題。jQuery相關的後續我會分章節逐一講述,這裏不作展開。
Promise異步編程總結起來就是實現了thenable,說的更直白的一點就是我跟你是好朋友,我可能有事須要你幫忙,你隨時候着吧,有須要我會第一時間通知你。你跟小王也是好哥們,並一樣告訴你須要他幫忙,並第一時間通知他。固然你通知他是在我通知你之後。用僞代碼表示:
myself(function(){ callXiaoLi(); }) .then(function(){ //我是小李,你喊我了,我就去喊小王 callXiaowang(); }) .then(function(){ console.log('我是小王'); });
一樣,下面咱們會用簡單的代碼實現以上邏輯。
/** * author by 素人漁夫 * email:dengxiaoming1217@gmail.com * description: 異步編程promise實現 */ (function() { /* * promise */ function EasyPromise() { this._callbacks = []; }; EasyPromise.prototype = { then: function(func, context) { var p; if (this._isdone) { p = func.apply(context, this.result); } else { p = new EasyPromise(); this._callbacks.push(function() { //此時apply context很重要,不然,res中的實例獲取不到this var res = func.apply(context, arguments); if (res && typeof res.then === 'function') { res.then(p.resolve, p); }else{ p.resolve(res); }; }); } return p; }, resolve: function() { this.result = arguments; this._isdone = true; for (var i = 0; i < this._callbacks.length; i++) { this._callbacks[i].apply(this.result, arguments); } this._callbacks = []; } }; EasyPromise.when = function() { var AllPromises = [].slice.call(arguments, 0)[0]; var p = new EasyPromise(); var promiseLen = AllPromises.length; var doneParams = {}; AllPromises.forEach(function(item, index) { item.__promise__name = index; doneParams[index] = {}; item.then(checkAllPromiseDone, item); }); var donePromiseCount = 0; function checkAllPromiseDone(data) { donePromiseCount++; doneParams[this.__promise__name] = data; if (donePromiseCount == promiseLen) { doneParams = makeArray(doneParams); p.resolve(doneParams); } }; return p; }; if (typeof define !== 'undefined') { define([], function() { return EasyPromise; }); } else if (typeof exports !== 'undefined') { exports.EasyPromise = EasyPromise; } else { window.EasyPromise = EasyPromise; } })();
then實現比較繞,須要解釋一下,如:.then(xx).then()
每次.then時會生成一個新的promise對象, 就是說後一個then是註冊給上一個promise對象的,其出發條件依賴於上一個.then時生成的promise。
若是上一個.then 最後返回結果是非promise對象(即沒有then方法),則直接 resolve
若是上一個.then最後返回的結果是promise對象,那.then生成的promise必須依賴返回結果中的promise,及內層的 promise.then( ".then時生成的promise".resolve)
可能我這麼解釋仍是沒能解釋很清楚,本身去理解吧。
另外,谷歌瀏覽器已經實現了Promise API。我使用的式chrome 版本 31.0.1650.63。不行你能夠在控制檯輸入如下代碼測試:
var p = new Promise(function(resolve,reject){ setTimeout(resolve,100); setTimeout(reject,300); }); p.then(function(){ alert('resolve'); },function(){ alert('reject'); });
好了,異步編程的方式就講這麼多,我這裏只不過是拋磚引玉,並無詳細闡述其api,而是以本身的代碼簡單實現其原來。這些異步編程方式不只僅限於瀏覽器環境,服務器環境如nodejs也可以使用。好比EventProxy原本就是在nodejs中發展而來的。
但願經過個人簡單講述能給你們提供幫忙。若是有錯,請幫忙指出,謝謝。
1.http://www.html5rocks.com/en/tutorials/es6/promises/
2.http://sporto.github.io/blog/2012/12/09/callbacks-listeners-promises/
3.http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html