angular中的$q是用來處理異步的(主要固然是http交互啦~).css
$q採用的是promise式的異步編程.什麼是promise異步編程呢? html
異步編程最重要的核心就是回調,由於有回調函數,因此才構成了異步編程,而回調有三個關鍵部分:node
一是何時執行回調,二是執行什麼回調,三是回調執行的時候傳入什麼參數.jquery
就以最多見的jquery Ajax舉例,發送一個請求後:git
何時執行回調: 請求成功(或者失敗)的時候github
執行什麼回調: 根據請求成功或者失敗,執行相應的回調函數編程
回調執行的時候傳入的什麼參數: 也就是後臺返回的數據api
在過去大多數場景下,咱們的異步編程都是這樣的格式:數組
function a(callback1,callback2){ var bool; var data; /*a函數要作的事情,作完後會判斷bool是true仍是false,而且給data賦值*/; //a函數完事兒,根據a函數的執行結果執行相應的回調函數 if(bool){ callback1(data) } if(!bool){ callback2(data) } } a(function(data){ /*回調函數1的處理*/ },function(data){ /*回調函數2的處理*/ } )
運行: http://jsfiddle.net/s2ebjon0/promise
這個例子只有一次回調,但若是回調中還要嵌套回調:
function a(callback1,callback2){ var bool; var data; /*a函數要作的事情,作完後會判斷bool是true仍是false,而且給data賦值;*/ bool=true; data='code_bunny' //a函數完事兒,根據a函數的執行結果執行相應的回調函數 if(bool){ callback1(data,function(data){ console.log('success:'+data) },function(data){ console.log('fial:'+data) }) } if(!bool){ callback2(data) } } a(function(data,callback1,callback2){ alert('成功'+data); var dataNew; var bool; dataNew = data; bool = false; if(bool){ callback1(data) } if(!bool){ callback2(data) } },function(data){ /*回調函數2的處理*/ alert('失敗'+data) } )
運行: http://jsfiddle.net/kbyy73dn/1/
我就不接着寫若是回調中嵌套回調再嵌套回調再...
總之一句話,使用傳統的回調函數做爲參數來編寫方式來實現異步,是十分麻煩的,代碼可讀性十分的差.而promise式的編程則把這個過程抽象化,只關注上面說到的三個關鍵點(何時執行回調,執行什麼回調,回調執行的時候傳入什麼參數),在這篇文章不關心它到底是如何作到的,只關心它是怎麼使用的:
promise式異步有兩個重要的對象,一個defer對象,一個promise對象,每一個defer對象都有和它綁定的promise對象,他們之間的關係是一一對應的.defer對象負責告知promise對象何時執行回調,執行什麼回調,回調執行的時候傳入什麼參數,而promise對象負責接收來自defer對象的通知,而且執行相應的回調.
舉個最簡單的例子:
var HttpREST = angular.module('Async',[]); HttpREST.controller('promise',function($q,$http){
//建立了一個defer對象; var defer = $q.defer();
//建立了defer對象對應的promise var promise = defer.promise;
//promise對象定義了成功回調函數,失敗回調函數 promise.then(function(data){console.log('成功'+data)},function(data){console.log('失敗'+data)});
//對promise發起通知: 1.執行這段代碼的時候就是執行回調的時候, 2.調用resolve方法,表示須要被執行的是成功的回調, 3.resolve裏的參數就是回調執行的時候須要被傳入的參數 defer.resolve('code_bunny') });
下面來看下$q的完整api
$q的方法:
一. $q.defer():
返回一個對象.通常把它賦值給defer變量:
var defer = $q.defer()
※defer的方法:
(一)defer.resolve(data)
對promise發起通知,通知執行成功的回調,回調執行的參數爲data
(二)defer.reject(data)
對promise發起通知,通知執行失敗的回調,回調執行的參數爲data
(三)defer.notify(data)
對promise發起通知,通知執行進度的回調,回調執行的參數爲data
※defer的屬性:
(一)defer.promise
※defer.promise的屬性:
1.defer.promise.$$v
promise的$$v對象就是對應的defer發送的data,當defer尚未發送通知時,$$v爲空.
有一點很重要,假設,咱們令$scope.a = defer.promise,那麼頁面在渲染{{a}}時,使用的是a.$$v來渲染a這個變量的.而且修改a變量,視圖不會發生變化,須要修改a.$$v,視圖纔會被更新,具體請參考:
http://www.cnblogs.com/liulangmao/p/3907307.html
※defer.promise的方法:
1.defer.promise.then([success],[error],[notify]):
.then方法接受三個參數,均爲函數,函數在接受到defer發送通知時被執行,函數中的參數均爲defer發送通知時傳入的data.
[success]: 成功回調,defer.resolve()時調用
[error]: 失敗回調,defer.reject()時調用
[notify]: 進度回調,defer.notify()時調用
.then()方法返回一個promise對象,能夠接續調用.then(),注意,不管.then()是調用的success函數,仍是error函數,仍是notify函數,發送給下一個promise對象的通知必定是成功通知,而參數則是函數的返回值.也就是說,then()方法裏的函數被執行結束後,即爲下一個promise發送了成功通知,而且把返回值做爲參數傳遞給回調.
eg1: (單次調用)
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> </div> </body> </html>
js:
var HttpREST = angular.module('Async',[]); //defer.resolve(),defer.reject(),defer.notify() HttpREST.controller('promise',function($q,$http,$scope){ var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise promise.then(function(data){$scope.name='成功'+data},function(data){$scope.name='失敗'+data},function(data){$scope.name='進度'+data}); $http({ method:'GET', url:'/name' }).then(function(res){ defer.resolve(res.data) },function(res){ defer.reject(res.data) }) });
若是正確建立後臺對於'/name'的請求處理,在一秒後返回'code_bunny',則一秒後頁面顯示:
若是後臺沒有建立對於'/name'的請求處理,則頁面直接顯示:
eg2: (鏈式調用)
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> <h3>{{name2}}</h3> </div> </body> </html>
js:
var HttpREST = angular.module('Async',[]); //.then()的鏈式調用 HttpREST.controller('promise',function($q,$http,$scope){ var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise promise.then(function(data){ $scope.name='成功'+data; return data+'2' },function(data){ $scope.name='失敗'+data; return data+'2' },function(data){ $scope.name='進度'+data; return data+'2' }).then(function(data){ $scope.name2 = '成功'+data },function(data){ $scope.name2 = '失敗'+data }); $http({ method:'GET', url:'/name' }).then(function(res){ defer.resolve(res.data) },function(res){ defer.reject(res.data) }) });
若是正確建立後臺對於'/name'的請求處理,在一秒後返回'code_bunny',則一秒後頁面顯示:,能夠看到,第一個.then()的成功的回調返回的data+'2'這個值,被傳到了下一個.then()的成功回調的data參數中
若是後臺沒有建立對於'/name'的請求處理,則頁面直接顯示:,能夠看到,就算第一個.then()調用的是失敗回調,可是它發給下一個promise的通知依然是成功通知,data值就是失敗回調的返回值
2.defer.promise.catch([callback])
至關於.then(null,[callback])的簡寫. 直接傳入失敗回調.返回一個promise對象.發給下一個promise對象的通知依然是成功通知.data值就是回調的返回值.
*很早的angualr版本是沒有這個方法的.
eg:
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> <h3>{{name2}}</h3> </div> </body> </html>
js:
//.catch() HttpREST.controller('promise', function ($q, $http, $scope) { var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise promise.catch(function (data) { $scope.name = data; return data+2 }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); $http({ method: 'GET', url: '/name' }).then(function (res) { defer.resolve(res.data) }, function (res) { defer.reject(res.data) }) });
後臺不建立'/name'的get請求響應, 獲得的結果以下:
能夠看到,promise對象收到通知,執行失敗回調,而後返回新的promise,對新的promise來講,收到的通知仍是執行成功回調.回調的參數是catch裏的函數的返回值.若是寫成:
promise.then(null,function (data) { $scope.name = data; return data+2 }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data });
二者是徹底一致的.
注意,若是後臺正確建立了'/name'的get請求響應, 那麼,獲得的結果會是:
也就是說,catch()方法若是收到的通知不是執行失敗回調,而是執行成功回調,它直接返回一個promise對象進行鏈式調用,等於把成功通知傳給了下一個promise.
因爲catch([callback])方法獲得的和.then(null,[callback])方法是徹底一致的,代碼上也米有精簡多少,因此通常就直接用.then就行了.
3.defer.promise.finally([callback])
.finally只接受一個回調函數,並且這個回調函數不接受參數.不管defer發送的通知是成功,失敗,進度,這個函數都會被調用.
.finally也返回一個promise對象,和上面兩個方法不一樣的是,它爲下一個promise對象發送的通知不必定是成功通知,而是傳給finally的通知類型.也就是說,若是defer給promise發送的是失敗通知,那麼,finally()獲得的promise它收到的也會是失敗通知,獲得的參數也不是finally的返回值,而是第一個defer發出的通知所帶的data.
*很早的angualr版本是沒有這個方法的.
eg:
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> <h3>{{name2}}</h3> </div> </body> </html>
js:
//.finally() HttpREST.controller('promise', function ($q, $http, $scope) { var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise promise.finally(function () { $scope.name = '已接收通知'; return 'code_dog'; }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); $http({ method: 'GET', url: '/name' }).then(function (res) { defer.resolve(res.data) }, function (res) { defer.reject(res.data) }) });
後臺不建立'/name'的get請求響應, 獲得的結果以下:
後臺正確建立'/name'的get請求響應, 獲得結果以下:
能夠看到,當promise收到通知的時候執行了fianlly裏的回調,而後返回的promise收到的通知和第一個promise收到的通知是一致的,不會受到finally中的回調的任何影響.
-------------------------------------------------------------------------------------------------------------------------------------------------------
二. $q.reject(data):
這個方法(在個人認知範圍裏),就只能在promise的.then(funciton(){})函數裏面調用.做用是給.then()返回的下一個promise發送錯誤信息,而且給錯誤回調傳入參數data
eg1:(.then方法裏使用)
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> <h3>{{name2}}</h3> </div> </body> </html>
js:
HttpREST.controller('promise', function ($q, $http, $scope) { var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise promise.then(function (data) { return $q.reject(data+'2') },function(){ return $q.reject(data+'2') }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); $http({ method: 'GET', url: '/name' }).then(function (res) { defer.resolve(res.data) }, function (res) { defer.reject(res.data) }) });
後臺正確建立'/name'的get請求響應時,獲得的結果是:
後臺沒有正確建立'/name'的get請求響應時,獲得結果:
能夠看到,在then()方法的函數中,用$q.reject(data)來包裝返回值,能夠給下一個返回的promise發送失敗通知併發送data參數.因此不管promise收到的是成功通知仍是失敗通知,下一個promise收到的都是失敗通知.
eg2:(.finally方法裏調用)
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> <h3>{{name2}}</h3> </div> </body> </html>
js:
HttpREST.controller('promise', function ($q, $http, $scope) { var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise promise.finally(function () { return $q.reject('code_dog') }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); $http({ method: 'GET', url: '/name' }).then(function (res) { defer.resolve(res.data) }, function (res) { defer.reject(res.data) }) });
不管後臺是否正確建立'/name'的get請求響應,獲得結果都是:.由於.finally()的回調是不能接受到data參數的.因此返回值都是同樣.
上面已經說過,使用.finally方法的時候,回調是不能接受參數的,回把對promise發的通知原封不動的(成功失敗,data)發送給下一個promise對象.
可是,若是我在.finally的回調裏用$q.reject(data)來包裝了返回值,那麼發送給下一個promise的通知會以$q.reject(data)爲準,也就是'失敗通知',回調參數爲data.
三. $q.all([promise1,promise2,...]):
$q.all接受一個數組類型的參數,數組的值爲多個promise對象.它返回一個新的promise對象.
當數組中的每一個單一promise對象都收到了成功通知,這個新的promise對象也收到成功通知(回調參數是一個數組,數組中的各個值就是每一個promise收到的data,注意順序不是按照單個promise被通知的順序,而是按照[promise1,promise2]這個數組裏的順序)
當數組中的某個promise對象收到了失敗通知,這個新的promise對象也收到失敗通知,回調參數就是單個promise收到的失敗通知的回調參數
eg:
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> <style type="text/css"> h4 { color:red } </style> </head> <body> <div ng-controller = "promise"> <h3>{{name1}}</h3> <h3>{{name2}}</h3> <h3>{{age1}}</h3> <h3>{{age2}}</h3> <h4>{{three}}</h4> </div> </body> </html>
js:
//$q.all() HttpREST.controller('promise', function ($q, $http, $scope) { var defer1 = $q.defer(); //建立了一個defer1對象; var promise1 = defer1.promise; //建立了defer1對象對應的promise1 var defer2 = $q.defer(); //再建立了一個defer2對象; var promise2 = defer2.promise; //建立了新的defer2對象對應的promise2 //promise1收到通知後執行的回調:給name1和name2賦值 promise1.then(function (data) { $scope.name1 = data; return data+'.2' },function(data){ $scope.name1 = data; return data+'.2' }).then(function (data) { $scope.name2 = 'promise1成功' + data }, function (data) { $scope.name2 = 'promise1失敗' + data }); //promise2收到通知後執行的回調:給age1和age2賦值 promise2.then(function (data) { $scope.age1 = data; return data+'.2' },function(data){ $scope.age1 = data; return data+'.2' }).then(function (data) { $scope.age2 = 'promise2成功' + data }, function (data) { $scope.age2 = 'promise2失敗' + data }); //建立一個promise3,它依賴於promise1和promise2 var promise3 = $q.all([promise1,promise2]); promise3.then(function(data){ $scope.three = data; },function(data){ $scope.three = data; }); $http({ method: 'GET', url: '/name' }).then(function (res) { defer1.resolve(res.data) }, function (res) { defer1.reject(res.data) }); $http({ method: 'GET', url: '/age' }).then(function (res) { defer2.resolve(res.data) }, function (res) { defer2.reject(res.data) }) });
(1)後臺node正確建立兩個get請求的響應:
app.get('/name',function(req,res){ setTimeout(function(){res.send('code_bunny')},2000) }); app.get('/age',function(req,res){ setTimeout(function(){res.send('18')},1000) });
一秒後顯示:
兩秒後顯示:
能夠看到,當promise1和promise2都收到成功通知後,promise3也收到成功通知,而它的回調的參數data就是一個數組,數組裏的兩個值分別是promise1收到的data和promise2收到的data,注意順序,這裏先收到通知的是promise2,可是promise3的data數組裏值的順序和promise收到通知的順序無關.只和$q.all([])這個數組裏的順序一致.
(2)後臺node只建立了'/name'一個get請求的響應:
顯示結果:
能夠看到,因爲'/age'請求錯誤,promise2被通知失敗,因此promise3也馬上被通知失敗,收到的data參數也和promise2收到的data一致
四. $q.when(obj,[success],[error],[notify]):
.when接受四個參數,其中,第二,第三,第四個參數都是函數,至關於promise.then()裏面的三個參數. 第一個參數有兩種可能:
1. 第一個參數不是promise對象: 直接調用成功回調,回調的參數就是第一個參數自己
eg:
html:
//$q.when() HttpREST.controller('promise', function ($q, $http, $scope) { $q.when('code_dog',function(data){ $scope.name = data; return data+'2' },function(data){ $scope.name = data; return data+'2' }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); });
顯示結果:
.when()的第一個參數不是promise對象,而是字符串'code_dog',因此它直接執行成功回調,也會返回下一個promise進行鏈式調用, 和通常的.then()是沒有區別的.
在這種狀況下其實不須要傳入第三,第四個參數,由於第一個參數若是不是promise,那麼它只會執行成功回調.
2. 第一個參數是一個promise對象:
當這個promise對象收到通知的時候,調用回調.回調就是第二,三,四個參數...(至關於.then([success],[error],[notify]))
另外,.when()返回的對象也就至關於.then()返回的對象.都是一個新的promise對象,均可以接收到回調發送的通知和參數...
能夠理解爲,
var defer = $q.defer();
defer.promise.then([success],[error],[notify])
這一段也能夠寫成:
var defer = $q.defer();
$q.when(defer.promise,[success],[error],[notify])
eg:
html:
<!DOCTYPE html> <html ng-app = 'Async'> <head> <title>19. $q異步編程</title> <meta charset="utf-8"> <script src="angular.js"></script> <script src="script.js"></script> </head> <body> <div ng-controller = "promise"> <h3>{{name}}</h3> <h3>{{name2}}</h3> </div> </body> </html>
js:
//$q.when() HttpREST.controller('promise', function ($q, $http, $scope) { var defer = $q.defer(); //建立了一個defer對象; var promise = defer.promise; //建立了defer對象對應的promise $q.when(promise,function(data){ $scope.name = data; return data+'2' },function(data){ $scope.name = data; return data+'2' }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); /* 這樣寫獲得的結果也是等價的. promise.then(function(data){ $scope.name = data; return data+'2' },function(data){ $scope.name = data; return data+'2' }).then(function (data) { $scope.name2 = '成功' + data }, function (data) { $scope.name2 = '失敗' + data }); */ $http({ method: 'GET', url: '/name' }).then(function (res) { defer.resolve(res.data) }, function (res) { defer.reject(res.data) }); });
和註釋掉的那一段寫法徹底等價.
因此,在這種狀況下.when()只是對.then()的一個包裝.
最後,api裏面說到:defer對象發送消息不會當即執行的,而是把要執行的代碼放到了rootScope的evalAsync隊列當中,當時scope.$apply的時候纔會被promise接收到這個消息。
雖然不太明白這段話的意思,我的理解是大多數時候,scope是會自動$apply的...若是在何時遇到promise沒有收到通知,那麼就試試看scope.$apply執行一下.
完整代碼:https://github.com/OOP-Code-Bunny/angular/tree/master/OREILLY/19%20%24q%E5%92%8Cpromise