AngularJS之道 - Promise編程模式

轉自:
lxjwlt's blog(2015-06-23):http://blog.lxjwlt.com/front-...html

AngularJS經過內置的$q服務提供Promise編程模式。經過將異步函數註冊到promise對象,Promise編程模式提供一種鏈式調用異步函數的方式。angularjs

初始化:ajax

<html>
    <head>
        <title>Promise fun</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
        <script src="app.js"></script>
    </head>
    <body ng-app="app">
    </body>
</html>

function getData($timeout, $q) {
  return function() {
    var defer = $q.defer()

    // 模擬異步函數
    $timeout(function() {
    }, 2000)

    return defer.promise
  }
}

angular.module('app', [])
    .factory('getData', getData)
    .run(function(getData) {
        var promise = getData();
    })

爲了簡單起見,咱們使用$timeout()服務來模擬異步函數。但實際而言,Promise模式最多見的應用場景是運用$http服務的AJAX回調函數。編程

defer對象api

defer對象只是一種暴露promise對象和promise對象相關方法的對象。defer對象經過調用$q.deferred()方法構造出來,並且defer對象暴露了三個主要方法:resolve(),reject(),和notify()。對應的promise對象能夠經過.promise屬性訪問。promise

咱們可使用defer對象發送「異步函數成功完成」的信號:app

function getData($timeout, $q) {
  return function() {
    var defer = $q.defer()

    // 模擬異步函數
    $timeout(function() {
      defer.resolve('data received!')
    }, 2000)

    return defer.promise
  }
}

這裏,咱們先建立了一個新的defer對象,而後返回它的.promise屬性。同時,咱們執行咱們的異步函數,當在該函數完成的時候,咱們執行defer對象的resolve()方法。resolve()函數的參數會被傳遞給接下來的回調函數。框架

promise對象dom

如今,咱們獲得了一個promise對象(經過defer.promise獲得),接下來,讓咱們註冊一個回調函數,該回調函數會在異步函數執行完成後被調用。異步

使用then()方法爲promise對象綁定一個回調函數,該回調函數將返回的字符串打印出來:

.run(function(getData) {
  var promise = getData()
    .then(function(string) {
      console.log(string)
    })
})

如今,當你刷新頁面,兩秒後你會看到控制檯打印出"data recived!"。

reject一個promise對象

注:簡而言之,promise的resolve是發出「執行成功」的信號,而reject則是發出「執行失敗」的信號。當信號發出,promise會根據不一樣的信號,執行不一樣的回調函數。

咱們已經知道如何resolve一個promise對象,但若是一個異步函數調用失敗了,會怎麼樣呢?

咱們使用Math.random()函數模擬promise對象有50%的機會被reject:

function getData($timeout, $q) {
  return function() {
    var defer = $q.defer()

    // 模擬異步函數
    $timeout(function() {
      if(Math.round(Math.random())) {
        defer.resolve('data received!')
      } else {
        defer.reject('oh no an error! try again')
      }
    }, 2000)
    return defer.promise
  }
}

then()方法的第二個參數能夠(可選的)接受一個錯誤處理回調函數,當promise被reject時纔會調用該回調函數。

將錯誤處理函數做爲第二參數傳給then():

.run(function(getData) {
  var promise = getData()
    .then(function(string) {
      console.log(string)
    }, function(error) {
      console.error(error)
    })
})

如今,若是你再次刷新頁面,你有50%的概率能看到錯誤消息!

經過調用多個不一樣then()方法,在同一個promise對象上能夠註冊多個回調函數。這些函數會按照他們註冊的順序一一被調用。

使用$q構造函數

$q服務自己也是一個函數,它可以讓你快速的將一個異步回調函數轉換成一個基於Promise模式的函數。

將這個模擬異步函數重寫成一個使用$q()返回promise對象的函數:

function getData($timeout, $q) {
  return function() {

    // 模擬異步函數
    return $q(function(resolve, reject) {
      $timeout(function() {
        if(Math.round(Math.random())) {
          resolve('data received!')
        } else {
          reject('oh no an error! try again')
        }
      }, 2000)
    })

  }
}

這個方法實現的效果跟手動建立defer對象的效果是同樣的 -- 你採用哪一種方式取決於你的偏好和你是否想要在代碼中使用notify()。

finally方法

Promise模式保證成功回調函數和錯誤回調函數中,其中一定有一個會被執行,但二者永遠不會同時執行。若是你須要確保不論promise對象的結果如何,都執行某一個特殊的函數,那該怎麼辦?你能夠在promise對象上註冊一個finally()方法。對於將代碼重置爲可知狀態下,這方法是很是有幫助的。

使用finally()方法將異步函數完成時的時間戳打印出來:

.run(function(getData) {
  var promise = getData()
    .then(function(string) {
      console.log(string)
    }, function(error) {
      console.error(error)
    })
    .finally(function() {
      console.log('Finished at:', new Date())
    })
})

無論promise對象是被resolve仍是被reject,你都能看到控制檯打印出當前時間。

Promise鏈式編程

Promise模式一個很是強大的特性是可以鏈式編寫回調函數。這個特性使數據可以在回調鏈的每一步上進行傳遞,處理和改變。雖然這一語法很是容易理解,但有時候這語法也會使人困惑。

讓咱們先看一個基礎例子。

首先,咱們對咱們的異步函數進行修改,咱們給resolve函數傳入一個0-9之間的隨機數(再也不是"data received"字符串):

function getData($timeout, $q) {
  return function() {
    // 模擬異步函數
    return $q(function(resolve, reject) {
      $timeout(function() {
        resolve(Math.floor(Math.random() * 10))
      }, 2000)
    })
  }
}

當頁面刷新,你應該可以看到一個0-9之間的整數被打印出來。

爲了鏈式調用,咱們須要修改回調函數,使其可以返回一個值。

修改promise回調函數,使其返回上述隨機數乘以2的值:

.run(function(getData) {
  var promise = getData()
    .then(function(num) {
      console.log(num)
      return num * 2
    })
})

如今,咱們可使用then()函數將另外一個回調函數綁定到咱們的promise對象上,該函數會在第一個回調函數返回值時被調用。上述隨機數兩倍的值會傳遞到第二個回調函數中:

.run(function(getData) {
  var promise = getData()
    .then(function(num) {
      console.log(num)
      return num * 2
    })
    .then(function(num) {
      console.log(num) // = random number * 2
    })
})

雖然這只是一個簡單的例子,可是它闡述了一個很是強大的概念。此外,你不但能夠從promise回調函數中返回一個簡單的值,你還可以返回一個新的promise對象。那麼,promise鏈會「暫停」直到這個返回的promise對象被resolve。這個特性使你可以鏈式編寫多個異步函數調用(好比多個服務端請求)。

總結

在angularJS框架中,Promise模式編程已經發揮起重要的做用,隨着ES6的發佈,未來Promise模式在JavaScript中的做用也會愈來愈重要。雖然它一開始看起來難以理解(尤爲是鏈式調用),但promise模式爲解決異步代碼提供了一套直觀且簡潔的接口,也正因如此,Promise成爲了現代JavaScript中的一個基礎構建模塊。

相關文章
相關標籤/搜索