$q
promise 是使用 AngularJS 時相當重要的一環,熟悉並活用之後可以讓許多原本看似較雜亂的 event-base 程式碼看起來謹然有序(當然也不是所有的 event-base 程式都適合使用 promise 來實現)。
排隊的需求
有時候我們會碰到一些特別的情況:
一段一段的動作是依序進行,但觸發的時候卻又是一次性的(比如說在一個 loop 中一次排定 10 個非同步的工作,需要依序執行)。
用程式碼表示大概是這樣:
var taskIds = [1, 2, 3, 4, 5];
angular.forEach(taskIds, function (taskId) {
// 以 $http 為例
$http.get('/someUrl', {params: {taskId: taskId}});
});
console.log('工作順利完成!');
然而我們真正想做的事情是這樣:
$http.get('/someUrl', {params: {taskId: 1}})
.then(function () {
return $http.get('/someUrl', {params: {taskId: 2}});
})
.then(function () {
return $http.get('/someUrl', {params: {taskId: 3}});
})
.then(function () {
return $http.get('/someUrl', {params: {taskId: 4}});
})
.then(function () {
return $http.get('/someUrl', {params: {taskId: 5}});
})
.then(function () {
console.log('工作順利完成!');
});
在上面的例子中,工作只有五項所以固然可以自己打一打程式碼解決,一般來說碰到的情況會有下面的困境:
- 工作項目數未知(為動態的或為參數)
- 同時可以進行的工作數不只一項(以上面為例即 1, 2 可以同時進行,而 3, 4 可以同時進行,最後再單獨執行 5 即可)
ngQueue
ngQueue
這個 module 就是為了因應上述的情況而誕生的,特色如下:
- 支援同步 / 非同步工作
- 與
$q
結合,並提供傳入 context 及 arguments 的功能 - 建立 queue 時可以指定可同時執行的工作數量
更多資訊可以去 http://github.com/pc035860/ngQueue 看看
開始使用 ngQueue
首先頁面內載入 ngQueue.js
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>
<script src="http://pc035860.github.io/ngQueue/ngQueue.min.js"></script>
接著在 AngularJS app 中載入 ngQueue
module
angular.module('myApp', ['ngQueue']);
排入工作
首先先用 ngQueue
提供的 $queueFactory
建立 queue instance
// 建立同時執行兩件工作的 queue
var queue = $queueFactory(2);
同步
接著可以排入同步的工作(即不是非同步的工作),範例中我們也嘗試傳入自訂的 context 以及 arguments。
queue.enqueue(function (inA, inB, inC) {
console.log(this); // {name: "context"}
console.log(inA, inB, inC); // hello world !
doSomething();
// 傳入 context 以及 arguments
}, {name: 'context'}, ['hello', 'world', '!']);
非同步
此類工作與同步工作在排入時最大的區別在於,工作內容最後的 return 值必須是 promise。
下面以兩種為例,分別具有各自的代表性:
- 使用
$timeout
與$q
- 自訂的 promise 工作
// $timeout delay
queue.enqueue(function () {
var dfd = $q.defer();
$timeout(function () {
dfd.resolve();
// or dfd.reject()
}, 100);
return dfd.promise;
});
- 使用
$http
- AngularJS 內建就會 return promise 的工作
// $http request
queue.enqueue(function () {
return $http.get('/some/api/call')
.success(function () {
// do something if success
})
.error(function () {
// do something if error
});
});
Demo
這個 demo 演示了 ngQueue
處理排入的同步 / 非同步工作的實際情況。
可以透過上方的按鈕選擇排入「同步」、「非同步」以及「亂數 10 個」的工作,而 queue 的 instance 在建立的時候設定為同時能夠執行兩個工作,因此我們在 demo 中可以看到同時會有兩個數字在倒數。
任務完成
所以最後回頭修改本篇的第一個範例,得到我們使用 ngQueue
之後的版本。
需要注意的是,如果 taskId
不用 argument 的方式傳入 enqueue
的 function 的話,真的輪到該 function 執行的時候,會無法透過 closure 抓到正確的 taskId
。
var queue = $queueFactory(1),
taskIds = [1, 2, 3, 4, 5];
angular.forEach(taskIds, function (taskId) {
// enqueue with argument: taskId
queue.enqueue(function (taskId) {
// returns $http promise
return $http.get('/someUrl', {params: {taskId: taskId}});
}, null, [taskId]);
});
queue.enqueue(function () {
// all tasks finished
console.log('工作順利完成!')
});