作者:寸志
鏈接:https://zhuanlan.zhihu.com/p/19622332
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
早上,老爸說:“兒子,天氣如何?”
每周一早上,老爸問兒子下午的天氣情況,兒子可以到自家房子旁邊小山上使用望遠鏡來觀看。兒子在出發時許諾(Promise)老爸(會通知老爸天氣情況)。
此刻,老爸決定,如果天氣不錯,明天就出去捕魚,否則就不去。而且如果兒子無法獲得天氣預報的話,也不去捕魚。
30分鍾左右,兒子回來了,每周的結局都不一樣。
結局A:成功獲得了(retrieved)天氣預報,晴天 :)
兒子成功獲取了天氣預報,天空晴朗,陽光明媚!承諾(Promise)兌現了(resolved),於是老爸決定開始為周日的捕魚做准備。
結局B:同樣成功獲得了天氣預報,雨天:(
兒子成功獲得了天氣預報,只不過是烏雲密布,要下雨。承諾(Promise)兌現了(resolved),只是老爸決定呆在家里,因為天氣很糟糕。
結局C:沒法獲得天氣預報:-/
出了問題,兒子沒法得知天氣預報,因為霧很大,就算站在小山上也無法看清。兒子沒辦法對象他離開時許下的諾言, promise was rejected!老爸決定留下來,這並不值得冒險。
從編程的角度來看
在這種情況下,老爸是邏輯控制,他把兒子當做service來處理。
這里面的邏輯我們已經知道了,老爸要兒子給他預報天氣,但是兒子沒法馬上告訴他,於是許諾他會帶着天氣預報回來,老爸在兒子回來之前有其他的事情要做。當老爸從兒子那獲得天氣預報之后,再決定是打包出發還是呆在家里。這個故事的重點是,兒子去山頂上看天氣這件事並沒有妨礙(block)老爸干其他的事情。因此對於這種情況,最好的方式就是許諾,隨后再兌現或者不兌現。
我們可是使用Angular的then()指定老爸針對每種結果的對策。then()函數接受兩個函數作為參數:一個許諾對現時執行,一個在無法對現時執行。
Controller:FatherCtrl
老爸控制情況:
// function somewhere in father-controller.js var makePromiseWithSon = function () { // This service's function returns a promise, but we'll deal with that shortly SonService.getWeather() // then() called when son gets back .then(function (data) { // promise fulfilled if (data.forecast === 'good') { prepareFishingTrip(); } else { prepareSundayRoastDinner(); } }, function (error) { // promise rejected, could log the error with: console.log('error', error); prepareSundayRoastDinner(); }); };
Service:SonService
兒子就是一個服務,他爬上小山,嘗試預報天氣。我們可以把兒子通過望遠鏡查看即將到來的天氣,假設成條用一個天氣的API,通常是異步的。要么獲得答案,要么出現問題(比方說返回500,大霧皚皚的天空)。
“捕魚天氣API”通過一個許諾返回結果,如果兌現的話,結果的格式像 { "forecast": "good" }這樣。
app.factory('SonService', function ($http, $q) { return { getWeather: function () { // the $http API is based on the deferred/promise APIs exposed by the $q service // so it returns a promise for us by default return $http.get('http://fishing-weather-api.com/sunday/afternoon') .then(function (response) { if (typeof response.data === 'object') { return response.data; } else { // invalid response return $q.reject(response.data); } }, function (response) { // something went wrong return $q.reject(response.data); }); } }; });
總之
老爸讓兒子預報的天氣就是一個比喻,說明了異步的本質。老爸並不想在門口傻等着兒子回來,他還有別的事情要做。而是在門口許個諾言,決定在3種不同的情況(天氣好/壞/不知道)下該怎么辦,兒子離開時就許下一個諾言,在他回來的時候會兌現或者食言。
兒子需要處理一個異步的API(通過望遠鏡來查看天空/使用天氣API)來獲得天氣,但這些對他老爸來說都是未知的,是誰不懂科技!?
原文:Promises in AngularJS, Explained as a Cartoon
擴展: 看完之后對$q的用法想深入了,接着看下面,
在用JQuery的時候就知道 promise 是 Js異步編程模式的一種模式,但是不是很明白他跟JQuery的deferred對象有什么區別。隨着公司項目的進行,要跟后台接數據了,所以決定搞定它。
Promise
Promise是一種模式,以同步操作的流程形式來操作異步事件,避免了層層嵌套,可以鏈式操作異步事件。
我們知道,在編寫javascript異步代碼時,callback是最最簡單的機制,可是用這種機制的話必須犧牲控制流、異常處理和函數語義化為代價,甚至會讓我們掉進出現callback大坑,而promise解決了這個問題。
ES6中Promise、angularJS內置的AngularJS內置Q,以及when采用的都是Promises/A規范,如下:
每個任務都有三種狀態:未完成(pending)、完成(fulfilled)、失敗(rejected)。
1.pending狀態:可以過渡到履行或拒絕狀態。
2.fulfilled狀態:不能變為其他任何狀態,而且狀態不能改變,必須有value值。
3.rejected狀態:不能變為其他任何狀態,而且狀態不能改變,必須有reason。
狀態的轉移是一次性的,狀態一旦變成fulfilled(已完成)或者failed(失敗/拒絕),就不能再變了。
function okToGreet(name){
return name === 'Robin Hood';
}
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
// 因為這個異步函數fn在未來的異步執行,我們把代碼包裝到 $apply 調用中,一邊正確的觀察到 model 的改變
$scope.$apply(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});
Q Promise的基本用法
上面代碼表示, $q.defer() 構建的 deffered 實例的幾個方法的作用。如果異步操作成功,則用resolve方法將Promise對象的狀態變為“成功”(即從pending變為resolved);如果異步操作失敗,則用reject方法將狀態變為“失敗”(即從pending變為rejected)。最后返回 deferred.promise ,我們就可以鏈式調用then方法。
JS將要有原生Promise,ES6中已經有Promise對象,firefox和Chrome 32 beta版本已經實現了基本的Promise API
AngularJs中的$q.defferd
通過 調用 $q.defferd 返回deffered對象以鏈式調用。該對象將Promises/A規范中的三個任務狀態通過API關聯。
deffered API
deffered 對象的方法
1.resolve(value):在聲明resolve()處,表明promise對象由pending狀態轉變為resolve。
2.reject(reason):在聲明resolve()處,表明promise對象由pending狀態轉變為rejected。
3.notify(value) :在聲明notify()處,表明promise對象unfulfilled狀態,在resolve或reject之前可以被多次調用。
deffered 對象屬性
promise :最后返回的是一個新的deferred對象 promise 屬性,而不是原來的deferred對象。這個新的Promise對象只能觀察原來Promise對象的狀態,而無法修改deferred對象的內在狀態可以防止任務狀態被外部修改。
Promise API
當創建 deferred 實例時會創建一個新的 promise 對象,並可以通過 deferred.promise 得到該引用。
promise 對象的目的是在 deferred 任務完成時,允許感興趣的部分取得其執行結果。
promise 對象的方法
1.then(errorHandler, fulfilledHandler, progressHandler):then方法用來監聽一個Promise的不同狀態。errorHandler監聽failed狀態,fulfilledHandler監聽fulfilled狀態,progressHandler監聽unfulfilled(未完成)狀態。此外,notify 回調可能被調用 0到多次,提供一個進度指示在解決或拒絕(resolve和rejected)之前。
2.catch(errorCallback) —— promise.then(null, errorCallback) 的快捷方式
3.finally(callback) ——讓你可以觀察到一個 promise 是被執行還是被拒絕, 但這樣做不用修改最后的 value值。 這可以用來做一些釋放資源或者清理無用對象的工作,不管promise 被拒絕還是解決。 更多的信息請參閱 完整文檔規范.
通過then()方法可以實現promise鏈式調用。
promiseB = promiseA.then(function(result) {
return result + 1;
});
// promiseB 將會在處理完 promiseA 之后立刻被處理,
// 並且其 value值是promiseA的結果增加1
$q的其他方法
$q.when(value):傳遞變量值,promise.then()執行成功回調
$q.all(promises):多個promise必須執行成功,才能執行成功回調,傳遞值為數組或哈希值,數組中每個值為與Index對應的promise對象
再深入點:
1. $q
$q是Angular的一種內置服務,它可以使你異步地執行函數,並且當函數執行完成時它允許你使用函數的返回值(或異常)。
2. defer
defer的字面意思是延遲,$q.defer() 可以創建一個deferred實例(延遲對象實例)。
deferred 實例旨在暴露派生的Promise 實例,以及被用來作為成功完成或未成功完成的信號API,以及當前任務的狀態。這聽起來好復雜的樣子,總結$q, defer, promise三者之間的關系如下所示。
var deferred = $q.defer(); //通過$q服務注冊一個延遲對象 deferred var promise = deferred.promise; //通過deferred延遲對象,可以得到一個承諾promise,而promise會返回當前任務的完成結果
defer的方法:
1. deferred.resolve(value) 成功解決(resolve)了其派生的promise。參數value將來會被用作promise.then(successCallback(value){...}, errorCallback(reason){...}, notifyCallback(notify){...})中successCallback函數的參數。
2. deferred.reject(reason) 未成功解決其派生的promise。參數reason被用來說明未成功的原因。此時deferred實例的promise對象將會捕獲一個任務未成功執行的錯誤,promise.catch(errorCallback(reason){...})。補充一點,promise.catch(errorCallback)實際上就是promise.then(null, errorCallback)的簡寫。
3. notify(value) 更新promise的執行狀態(翻譯的不好,原話是provides updates on the status of the promise's execution)
defer的小例子:
function asyncGreet(name) { var deferred = $q.defer(); //通過$q.defer()創建一個deferred延遲對象,在創建一個deferred實例時,也會創建出來一個派生的promise對象,使用deferred.promise就可以檢索到派生的promise。 deferred.notify('About to greet ' + name + '.'); //延遲對象的notify方法。 if (okToGreet(name)) { deferred.resolve('Hello, ' + name + '!'); //任務被成功執行 } else { deferred.reject('Greeting ' + name + ' is not allowed.'); //任務未被成功執行 } return deferred.promise; //返回deferred實例的promise對象 } function okToGreet(name) { //只是mock數據,實際情況將根據相關業務實現代碼 if(name == 'Superman') return true; else return false; } var promise = asyncGreet('Superman'); //獲得promise對象 //promise對象的then函數會獲得當前任務也就是當前deferred延遲實例的執行狀態。它的三個回調函數分別會在resolve(), reject() 和notify()時被執行 promise.then(function(greeting) { alert('Success: ' + greeting); }, function(reason) { alert('Failed: ' + reason); }, function(update) { alert('Got notification: ' + update); });
3. promise
當創建一個deferred實例時,promise實例也會被創建。通過deferred.promise就可以檢索到deferred派生的promise。
promise的目的是允許interested parties 訪問deferred任務完成的結果。
按照CommonJS的約定,promise是一個與對象交互的接口,表示一個動作(action)的結果是異步的,而且在任何給定的時間點上可能或不可能完成。(這句話好繞口,我的理解是promise相當於一個承諾,承諾你這個任務在給定的時間點上可能會完成,也可能完成不了。如果完成了那就相當於resolve, 如果未完成就相當於reject。不知道這樣理解對不對?)
promise 的方法:
1. then(successCallback, errorCallback, nitifyCallback) 根據promise被resolve/reject,或將要被resolve/reject,調用successCallback/errorCallback。
2. catch(errorCallback) then(null, errorCallback)的縮寫。
3. finally(callback, notifyCallback)
補充說明:
promise.then()會返回一個新的衍生promise,形成promise鏈。例如:
promiseB = promiseA.then(function(result) { return result + 1; }); // promiseB will be resolved immediately after promiseA is resolved and its value // will be the result of promiseA incremented by 1