Blackbing Playground

[javascript] 利用 Deferred 處理順序性任務

Javascript 的世界裡頭充滿了 callback,從其他語言進入 Javascript 的世界常會頭昏,甚至會有點排斥這樣的特性。本篇文章介紹 Deferred 物件的基礎用法,避免程式在做順序任務的處理時難以維護,在模組化的程式設計中,是很重要也很常用到的技巧。

Callback function

假設我們有一個 query 的 function,也許是 ajax,也許是 sql query,總之它接受命令做完回傳 callback

1
2
3
4
var query = function(task, callback) {
//do something
return callback(task + ' finished');
};

因此我們可以很簡單的呼叫

1
2
3
4
```javascript
query('task1', function(msg) {
console.log(msg); //task1 finished
}

但假設我們要依序執行 query 的話,例如 task1 -> task2 -> task3,很自然地就會寫成這樣:

1
2
3
4
5
6
7
8
9
10
// nested way
query('task1', function(msg) {
console.log(msg); //task1 finished
return query('task2', function(msg) {
console.log(msg); //task2 finished
return query('task3', function(msg) {
return console.log(msg); //task3 finished
});
});
});

function call function

這樣的問題就是巢狀的 function 裡頭要做的事情越來越複雜,就會將 function 寫成一大包,到最後很難維護。所以很直覺的就將要做的事情拆成 function來處理,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//funciton call function
var task1 = function() {
query('task1', function(msg) {
console.log('function ' + msg); //function task1 finished
return task2();
});
};
var task2 = function() {
query('task2', function(msg) {
console.log('function ' + msg); //function task2 finished
return task3();
});
};
var task3 = function() {
query('task3', function(msg) {
return console.log('function ' + msg); //function task3 finished
});
};
task1();

把 function 拆開看起來就不會有巢狀越寫越深的問題了,但是會遇到問題是:

  1. 若你的 function 散落各處,就會很難 trace 程式碼。
  2. function 與 function 之間的依賴性。越複雜的依賴性會讓程式日後難以維護。

那麼如何利用 Deferred 物件?

首先我們用 Deferred 方式把 query 包起來

1
2
3
4
5
6
7
8
var query_dfr = function(task) {
var _dfr;
_dfr = $.Deferred();
query(task, function(msg) {
return _dfr.resolve(msg);
});
return _dfr.promise();
};

接著我們就可以這樣呼叫

1
2
3
query_dfr('task1').done(function(msg){
console.log('Deferred ' + msg); //Deferred task1 finished
})

看起來沒什麼了不起,那接著來寫順序的任務吧:

1
2
3
4
5
6
7
8
9
query_dfr('task1').done(function(msg) {
console.log('Deferred ' + msg);
query_dfr('task2').done(function(msg) {
console.log('Deferred ' + msg);
query_dfr('task3').done(function(msg) {
console.log('Deferred ' + msg);
});
});
})

XDXDXD,翻白眼翻到外太空,當然不是這樣的,這樣還是在寫巢狀的程式。

Deferred and pipe

我們需要用pipe來串聯我們的任務:

1
2
3
4
5
6
7
8
9
query_dfr('task1').pipe(function(msg) {
console.log('Deferred ' + msg);
return query_dfr('task2');
}).pipe(function(msg) {
console.log('Deferred ' + msg);
return query_dfr('task3');
}).pipe(function(msg) {
return console.log('Deferred ' + msg);
});

這樣看起來就精簡多了,而且也很容易看懂 task 之間的順序關係。

Library

在瀏覽器環境中,可以直接使用 jQuery Deferred,若你不需要 jQuery,可以用 stand alone Deferred

另外在 node 環境中,可以使用 node-deferred

Reference

本範例程式碼:https://gist.github.com/blackbing/a9dad5c141f51b65b650

不只 pipe,Deferred 還有很多其他的用法,有興趣深入了解的話可以參考: