$q 的故事
與其很技術的說 $q 要如何使用,還不如說一個故事,如果大家能懂這個故事,基本上對 $q 就已經理解的差不多了。這個故事是這樣的...你是個投資家,常決定要把錢投資給哪個新公司。有一天你決定要投資某個新公司,所以你到銀行去要領錢,結果銀行剛好進行大盤點,行員說要明天一早你才能領錢。可是你一定要在今天簽訂投資的合約,不然這個新公司就有別人要投資了!這個故事基本上在說的就是整個 $q 操作的方式與流程,簡單的整理如下。
行員:「公司引進了一個新系統 $q 可以解決這個問題!」
行員先在新系統 $q 登記了一個請款記錄 ($q.defer()),之後系統 $q 吐出一張智慧保證卡 ($q.defer().promise),行員把智慧保證卡給您。
行員:「先生,這是智慧保證卡,這張卡會在隔天票款好時會自動通知持有人,屆時拿著這張卡就可以當做現金了。」
於是你拿著智慧保證卡簽成了投資新公司的合約,並且把這張智慧保證卡給了公司的負責人。
隔天銀行完成了大盤點的工作,並在系統 $q 看到你的請款記錄,在準備好票款後,銀行用智慧保證卡通知了公司負責人($q.defer().resolve(投資款項));公司負責人在收到智慧保證卡的通知後,就開始安心的採購他所需要的用品了 ($q.defer().promise.then(投資款項))。
銀行負責兩件事: (pseudo code)
1. 註冊請款:var cashCheckJob = $q.defer();
2. 準備好款項後,通知、給予款項: cashCheckJob.resolve(" Money is ready !");
你負責兩件事: (pseudo code)
1. 取得智慧保證卡:intelligentPromise = cashCheckJob.promise;
2. 智慧保證卡兌現後該做些什麼事: intelligentPromise.then(" Give company money. ");
有沒有覺得 $q 似乎沒這麼可怕了呢?現在我們可以來看看 code 了。
實際操作 $q
在 angularJS 裡要使用 $q,需要將其注入(inject)進相關的 controller 或 Service 裡。底下我們用一個簡單的範例來說明如何使用。var myApp = angular.module('myApp',[]); myApp.controller('MyCtrl', function($scope, $q, $timeout){ // 注入 $q 這個 Service, $timeout 是為了模擬銀行處理程序而注入 var bankCashCheck = function(cash){ // 銀行對領錢客戶的處理流程 var cashCheckJob = $q.defer(); // 行員向系統 $q 註冊請款 job var promiseCard = cashCheckJob.promise; // 請款 job 回給行員一個智慧保證卡 $timeout(function(){ // (Thread 2) 銀行進行的工作,這個動作是另一個 thread 處理了 alert("Yes! We got it!"); // (Thread 2) 銀行盤點完 cashCheckJob.resolve(cash); // (Thread 2) 通知智慧保證卡並給予款項 }, 1*1000); return promiseCard; // 行員將智慧保證卡給你 } $scope.getMoney = "N/A"; $scope.cash = function(cash){ // 你去銀行領錢 var promiseCard = bankCashCheck(cash); // 從行員手上拿到智慧保證卡 promiseCard.then(function(getCash){ // 智慧保證卡兌現後得到 getCash 這麼多錢之後要做的事 $scope.getMoney = getCash; }); /** 通常在開發時,不會特別設一個參數而是直接寫成一行 bankCashCheck(100).then(function(getCash){ $scope.getMoney = getCash; }); **/ }; });
為了讓大家更有感覺,這邊有一個 jsfiddle 的 Demo。
防錯功能
話說銀行並非慈善機構,萬一有人來跟他領 $100 元,而實際上該帳戶只有 $50 元,銀行當然要拒絕這筆交易。$q 系統當然也設計了這個功能,只要觸發該筆交易的 reject 功能即可。這時候銀行負責的不只兩件事,而多了一件:(pseudo code)1. 註冊請款:var cashCheckJob = $q.defer();
2. 確保帳戶有足夠金額,準備好款項後,通知、給予款項: cashCheckJob.resolve(" Money is ready !");
3. 帳戶金額不足,拒絕請款:cashCheckJob.reject(" Your Account has no enough money !");
而當時拿著智慧保證卡保證拿到前後可以做的事,不就都不能做了嗎?對於交易被拒絕,我們也要有一些處理才行,所以你也多了一件事,不過這件事要對應智慧保證卡所給的通知結果: (pseudo code)
1. 取得智慧保證卡:intelligentPromise = cashCheckJob.promise;
2. 智慧保證卡兌現或沒兌現後該做些什麼事: intelligentPromise.then(" Give company money. ", "Check my account");
我們來修改一下剛剛銀行的 code。
var myApp = angular.module('myApp',[]); myApp.controller('MyCtrl', function($scope, $q, $timeout){ var bankCashCheck = function(cash){ var cashCheckJob = $q.defer(); var promiseCard = cashCheckJob.promise; $timeout(function(){ alert("Yes! We got it!"); var accountMoney = 100; if(accountMoney >= cash){ // 檢查帳戶是否有足夠金額 cashCheckJob.resolve(cash); // 金額足夠:通知智慧保證卡並給予款項 }else{ // 金額不足:通知智慧保證卡並給予款項 cashCheckJob.reject('No enough money.'); } }, 1*1000); return promiseCard; // 行員將智慧保證卡給你 } $scope.getMoney = "N/A"; $scope.cash = function(cash){ // 你去銀行領錢 var promiseCard = bankCashCheck(cash); promiseCard.then(function(getCash){ // 智慧保證卡兌現後得到 getCash 這麼多錢之後要做的事 $scope.getMoney = getCash; }, function(cancelReason){ // 得到交易取消的通知和原因,可作其他處理 alert("Can't get money, because "+ cancelReason); // Can't get money, because No enough money. }); }; });
連續的 Promise
簽完約,投資的新公司從你那拿到智慧保證卡後,就立刻去買機器,公司負責人只能透過智慧保證卡付款給機器廠商。於是公司負責人用智慧保證卡給機器廠商一個承諾,保證在銀行兌現後,會付給廠商款項。之後廠商就可以把訂製的機器給他們。所以整個流程就變成:1. 取得智慧保證卡:intelligentPromise = cashCheckJob.promise;
2. 公司負責人用智慧保證卡給廠商承諾 (prmise.then() 會回傳一個新的 promise): var companyPromise = intelligentPromise.then(" Give company money.", "Check my account");
3. 廠商拿到公司負責人的promise後要做的事: companyPromise.then("Give/Not Give company machine.");
最後給大家一個完整的 DEMO:
總結:
最後幫大家再整理一下 $q, deferred, promise 的使用方法:1. var defer = $q.defer(); // 用 $q 產生一個 deffered
2. var result = defer.promise; // 把保證傳出去
3. defer.resolve(data); // 事件處理結束成功,defer 把資料傳出去
4. defer.reject(reason); // 事件處理結束失敗,defer 把原因傳出去
5. result.then(success, error); // defer 處理成功 call success, defer 處理失敗 call error
Note:
1. promise.then 本身會回傳一個 promise,因此promise可以延伸下去。
2. AngularJS $http service 也幾乎和 $q 的處理模式一樣。
以上,希望大家以後不會再一看到 $q 或 promise 就 QQ 了~
比喻得太好了,借分享~
回覆刪除謝謝!
刪除