angular學習筆記(二十九)-$q服務


angular中的$q是用來處理異步的(主要當然是http交互啦~).

$q采用的是promise式的異步編程.什么是promise異步編程呢? 

異步編程最重要的核心就是回調,因為有回調函數,所以才構成了異步編程,而回調有三個關鍵部分:

一是什么時候執行回調,二是執行什么回調,三是回調執行的時候傳入什么參數.

就以最常見的jquery Ajax舉例,發送一個請求后:

什么時候執行回調: 請求成功(或者失敗)的時候

執行什么回調: 根據請求成功或者失敗,執行相應的回調函數

回調執行的時候傳入的什么參數: 也就是后台返回的數據

在過去大多數場景下,我們的異步編程都是這樣的格式:

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/

這個例子只有一次回調,但如果回調中還要嵌套回調:

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 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM