轉自:
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中的一個基礎構建模塊。