angular學習筆記-$q服務

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 

相關文章
相關標籤/搜索