(本文章以as3代碼爲例)前端
在前端開發時,常常會使用到Ajax(Asynchronous Javascript And XML)請求向服務器查詢信息(get)或交換數據(post),ajax請求都是異步響應的,每次請求都不能同步返回結果,並且屢次請求嵌套在一塊兒時,邏輯很難處理,怎麼辦呢?git
在as3中,get請求的寫法一般以下github
public static function httpGet(url:String):void { var httpService:HTTPService =new HTTPService(); httpService.url= url; httpService.resultFormat="e4x"; httpService.method = URLRequestMethod.GET; httpService.addEventListener(ResultEvent.RESULT, onSuccess); httpService.addEventListener(FaultEvent.FAULT, onFail); httpService.send(); function onSuccess(result:ResultEvent):void { // do something
} function onFail(fault:FaultEvent):void { // alert error
} }
在ajax請求中,查詢成功的回調函數是異步返回的,沒法在HttpGet方法中返回結果,下一步的邏輯處理只能寫在onSuccess方法中,對於查詢結果的邏輯處理都要寫在查詢成功的回調函數內,若是業務邏輯很複雜的話,這種寫法就太麻煩了,並且不能複用。ajax
一種解決思路是經過消息來傳遞查詢結果,當查詢成功或失敗時,發送相應的消息和結果給該次查詢的監聽者,代碼以下(注意紅色加粗部分)編程
var eventBus:EventDispatcher = new EventDispatcher; public static function httpGetWithMessage(url:String, successMessage:String, failMessage:String):void { var httpService:HTTPService =new HTTPService(); httpService.url= url; httpService.resultFormat="e4x"; httpService.method = URLRequestMethod.GET; httpService.addEventListener(ResultEvent.RESULT, onSuccess); httpService.addEventListener(FaultEvent.FAULT, onFail); httpService.send(); function onSuccess(result:ResultEvent):void { eventBus.dispatchEvent(successMessage, result); } function onFail(fault:FaultEvent):void { eventBus.dispatchEvent(failMessage, fault); } } private function action(url:String):void { var successMSG:String = "success"; var failMSG:String = "fail"; eventBus.addEventListener(successMSG, onSuccess); eventBus.addEventListener(failMSG, onFail); httpGetWithMessage(url, successMSG, failMSG); } private function onSuccess(result:ResultEvent):void { // do something
} private function onFail(fault:FaultEvent):void { // alert error
}
經過消息機制的辦法,能夠把查詢成功和失敗的回調函數從action方法中提取出來,從而能夠複用這部分代碼,可是,使用消息機制仍然存在4個缺點:promise
1、必須有一個全局消息總線來控制全部的消息。當查詢次數多時,須要爲每次查詢定義不一樣的消息,還要考慮併發時同一個業務請求的消息不能相同,這種全局消息總線對於消息的管理代價太大;服務器
2、action方法仍然不能複用,每次不一樣的查詢都須要從新寫一個新的方法;併發
3、action方法仍然是異部處理,方法自己沒法返回查詢結果,以至程序的先後語意不連貫。當請求次數多時,對於一個業務邏輯的處理,必需要分開寫在不少個回調函數內,這些回調函數彼此之間也沒法溝通。框架
4、最重要的一點是,當一個業務邏輯處理須要屢次查詢時,每次查詢只能嵌套在上一次查詢的成功回調函數內,如此,除了最內層的查詢能夠複用,外內全部的查詢方法都不能複用,代碼極難維護,這時若是須要修改兩個查詢的前後順序,你就慘了。異步
Promise/Deferred模式 (協議/延時模式)
Promise/Deferred模式最先出如今Javascript的Dojo框架中,它是對異步編程的一種抽象。
promise的核心思想能夠歸納爲:把異部處理看做一個協議,不管異部處理的結果是成功仍是失敗,都把協議提早返回,等異部處理結束後,協議的接收者天然就知道結果是成功仍是失敗了。從形式上說,Promise/Deferred模式能夠把異部處理用同步的形式表達出來,極大方便了代碼維護,語意更加清晰,也方便了代碼複用。
這裏是兩個開源地址:
將文章最初的get請求方法用Promise/Deferred模式改寫一下,首先new一個延時,在發出請求後當即返回,此時這個延時的狀態是「未完成」,當異部請求成功後,回調函數會改變它的狀態爲「完成」或「失敗」並傳遞參數,這樣一來,異部邏輯就巧妙的變成了同步邏輯,代碼以下
public static function httpGet(url:String):Promise { var deferred:Deferred = new Deferred(); var httpService:HTTPService =new HTTPService(); httpService.url= url; httpService.resultFormat="e4x"; httpService.method = URLRequestMethod.GET; httpService.addEventListener(ResultEvent.RESULT, onSuccess); httpService.addEventListener(FaultEvent.FAULT, onFail); httpService.send(); return deferred.promise; function onSuccess(result:ResultEvent):void { deferred.resolve(result); } function onFail(fault:FaultEvent):void { deferred.reject(fault); } }
調用時能夠這樣寫:
public function process(url:String):void { var p:Promise = httpGet(url); p.then(doSomthing, doError); } public function doSomthing(result:Object):void { } public function doError(result:Object):void { }
最關鍵的一步就是then方法,當請求成功時,執行doSomthing,失敗時執行doError
經過這種方式,異部請求簡化到了腦殘的程度,好處有4點
1、不須要全局消息機制,省了一大陀工做量,且沒有併發問題;
2、請求方法自己與業務邏輯處理徹底分開,互不干擾,do something的部分徹底能夠放在另外的文件中來寫。不管是get請求仍是之後的業務邏輯處理方法都是可複用的;
3、請求方法自己直接返回結果,能夠同步處理查尋結果。
4、能夠鏈式調用、嵌套調用,想怎麼用就怎麼用~~~
如今假設業務邏輯要實現一個3次查詢的操做,每次查詢URL都依賴上一次的查詢結果,在沒有Promise/Deferred模式以前,只能用3層回調函數嵌套在一直,這簡直是惡夢,不過如今簡單多了,你能夠這樣寫:
public function process(url:String):void { var p1:Promise = httpGet(url); p1.then(action_1to2).then(action_2to3).then(action3); function action_1to2(result:Object):Promise { var url2:String = getUrl2(result); var p:Promise = httpGet(url2); return p; } function action_2to3(result:Object):Promise { var url3:String = getUrl3(result); var p:Promise = httpGet(url3); return p; } function action3(result:Object):void { // do something
} }
如上,3個get請求是串行的關係,只須要用then鏈把它們鏈接起來就能夠了,而後本身實現一下getUrl2和getUrl3兩個方法就大功告成了。假如此時需求變了,要求交換一下前兩次查詢的順序,你也只須要改動不多的代碼,爽不爽!我的認爲鏈式調用最有用的一點就是邏輯清晰,在視覺上把每一步要作的工做緊密放在一塊兒,一目瞭然,只要讀這一行代碼就知道第一部作什麼,第二步作什麼,第三步作什麼,維護也方便,比消息機制的回調函數強了無數倍。
最爽的還不僅如此,假如3個get請求是並行關係,你還能夠這樣寫:
public function process(url1:String, url2:String, url3:String):void { var p1:Promise = httpGet(url1); var p2:Promise = httpGet(url2); var p3:Promise = httpGet(url3); Promise.all([p1, p2, p3]).then(doSomething, doError); } public function doSomething(result:Array):void { var result0:Object = result[0]; var result1:Object = result[1]; var result2:Object = result[2]; // do something
} public function doError(fault:Fault):void { }
當3個請求所有成功時,執行doSomething,只要有一個請求失敗,則執行doError。
假設這時需求又變了,要求在查尋過程當中,前端顯示一個loading畫面,查尋結束後,畫面消失,你能夠這樣簡單的改一下代碼:
public function process(url1:String, url2:String, url3:String):void { showLoadingImage(); var p1:Promise = httpGet(url1); var p2:Promise = httpGet(url2); var p3:Promise = httpGet(url3); Promise.all([p1, p2, p3]).then(doSomething, doError).always(removeLoadingImage); } function doSomething(result:Array):void { var result0:Object = result[0]; var result1:Object = result[1]; var result2:Object = result[2]; // do something
} function doError(fault:Fault):void { }
always方法的含意是不管前面的協議成功或者失敗,都執行下一個方法。在Promise/Deferred模式的狀況下,你不用在3次請求的6個回調函數裏分別來執行removeLoadingImage方法,只需一次調用便可,是否是很方便呢?