基本上Flow Control並不是很困難的概念,加上伺服器端Javascript越來越多人使用,為了解決非同步時的Flow Control問題,許多Library就如雨後春筍般冒出...
接續上一篇:Javascript中非同步執行的一些問題,今天來看看到底有哪些Library,挑選幾個來看看到底要怎麼用...
因為使用的人多,node.js網站上有一個Flow Control的模組列表:
https://github.com/joyent/node/wiki/Modules#wiki-async-flow
上面洋洋灑灑幾十個,這要怎麼選阿
為了方便,先把這些大致分類一下:
接下來會簡單示範幾個使用傳統做法的例子,包括async以及step。另外兩類就只簡單介紹一下。
Step
這是node.js的元老級人物Tim Caswell寫的Flow Control Library,最初是一系列Flow Control介紹的文章,再把它發展成目前的Library,基本上已經沒有在維護,不過因為他還蠻簡單的,很快就可以透過原始碼來了解實作的原理。
簡單的循序執行,步驟一執行完畢,才會執行步驟二,依此類推:
Step(
function() {//步驟一
$.get('test876a.txt', this);
},
function(text) {//步驟二
return text.toUpperCase();
},
function(err, text) {//步驟三
var self = this;
setTimeout(function() {self(null, '[['+text+']]')}, 100);
},
function(err, text) {//步驟四
if(err) throw err;
$('#panel').html(text);
}
);
如果要等待數個非同步執行的函數結束,再執行另一個函數:
Step(
function() {//步驟一
var t1 = this.parallel();//處理非同步時,需要用到這個函數,他會調整函數計數
var t2 = this.parallel();//讓非同步函數執行完畢才會倒下一步驟
$.get('test876a.txt', function(data) {t1(null, data);});//傳進來接收結果
$.get('test876b.txt', function(data) {t2(null, data);});
},
function(err, a, b) {//步驟二,會等到步驟一直行完畢才執行
if(!err) {
$('#panel').html(
[a, b].map(function(t) {
return t.toUpperCase();
})
.map(function(t) {
return '[['+t+']]';
})
.join('<br>')
);
}
}
);
呼叫this.parallel()時,會調整內部的函數計數,再所有非同步函數執行完畢才會走到下一步。基本上Step不是為了jquery設計的,所以需要用一個函數來調整參數,跟github上面的範例就不太一樣了。
完整的程式:
<script src='http://code.jquery.com/jquery-1.9.1.min.js'></script>
<script src='https://raw.github.com/creationix/step/master/lib/step.js'></script>
<button id='btn1'>start serial</button>
<button id='btn2'>start async wait</button>
<div id='panel'></div>
<script>
$(function() {
$('#btn1').click(function() {
Step(
function() {
$.get('test876a.txt', this);
},
function(text) {
return text.toUpperCase();
},
function(err, text) {
var self = this;
setTimeout(function() {self(null, '[['+text+']]')}, 100);
},
function(err, text) {
if(err) throw err;
$('#panel').html(text);
}
);
});
$('#btn2').click(function() {
Step(
function() {
var t1 = this.parallel();
var t2 = this.parallel();
$.get('test876a.txt', function(data) {t1(null, data);});
$.get('test876b.txt', function(data) {t2(null, data);});
},
function(err, a, b) {
if(!err) {
$('#panel').html(
[a, b].map(function(t) {
return t.toUpperCase();
})
.map(function(t) {
return '[['+t+']]';
})
.join('<br>')
);
}
}
);
});
});
</script>
另外,test876a.txt的內容是"i'm a demo.",test876b.txt則是"i'm another demo."。按下start serial按鈕,會依序執行函數,取得test876a.txt內容,改成大寫,然後在前後加上中括號顯示在畫面。start async的過程類似,不過會讀兩個text檔,經過相同的處理後顯示在畫面。
async
這是Caolan McMahon開發的Library,最初是為了node.js開發,不過也可以用在Browser環境中。這是一套非常完整的實作,除了Flow Control,還提供許多非同步的陣列操作,以及一些協助工具。在許多node.js模組中都可以看到他的身影,用戶相當多。
先來看一個循序執行的例子,基本上是模仿上面Step的例子:
async.waterfall([
function(next) {//步驟一
$.get('test877a.txt', function(data) {
next(null, data);
});
},
function(text, next) {//步驟二
setTimeout(function() {
next(null, text.toUpperCase());
}, 0);
},
function(text, next) {//步驟三
setTimeout(function() {
next(null, '[['+text+']]');
}, 0);
}
], function(err, result) {//結果,步驟四
$('#panel').html(result);
});
由於async.serial並不會把前一個函數的處理結果傳進來,所以我改用waterfall來做。
非同步執行的話,可以呼叫async.parallel:
async.parallel([
function(next) {//非同步步驟一
$.get('test877a.txt', function(data) {
next(null, data);
});
},
function(next) {//非同步步驟二
$.get('test877b.txt', function(data) {
next(null, data);
});
}
], function(err, results) {//結果,會等到上述兩個操作結束才執行
async.waterfall([
function(next) {
setTimeout(function() {
next(null, results.map(function(text) {
return text.toUpperCase();
}));
}, 0);
},
function(texts, next) {
setTimeout(function() {
next(null, texts.map(function(t) {
return '[['+t+']]';
}));
}, 0);
},
], function(err, result) {
$('#panel').html(result.join('<br>'));
});
});
在兩個非同步的request完成後,就會交給下一個函數處理,在這裡又使用了一次async.waterfall,模仿之前的處理方式,只是處理的對象是字串陣列,然後再把結果顯示出來。
完整的程式如下:
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="https://raw.github.com/caolan/async/v0.2.5/lib/async.js"></script>
<button id='btn1'>start serial</button>
<button id='btn2'>start async</button>
<div id='panel'></div>
<script>
$(function() {
var r1;
$('#btn1').click(function() {
async.waterfall([
function(next) {
$.get('test877a.txt', function(data) {
next(null, data);
});
},
function(text, next) {
setTimeout(function() {
next(null, text.toUpperCase());
}, 0);
},
function(text, next) {
setTimeout(function() {
next(null, '[['+text+']]');
}, 0);
}
], function(err, result) {
$('#panel').html(result);
});
});
$('#btn2').click(function() {
async.parallel([
function(next) {
$.get('test877a.txt', function(data) {
next(null, data);
});
},
function(next) {
$.get('test877b.txt', function(data) {
next(null, data);
});
}
], function(err, results) {
async.waterfall([
function(next) {
setTimeout(function() {
next(null, results.map(function(text) {
return text.toUpperCase();
}));
}, 0);
},
function(texts, next) {
setTimeout(function() {
next(null, texts.map(function(t) {
return '[['+t+']]';
}));
}, 0);
},
], function(err, result) {
$('#panel').html(result.join('<br>'));
});
});
});
});
</script>
Javascript語法加強?
之前有看過的主要是streamlinejs以及<a href="">tamejs</a>。這幾個的構想,主要是希望能用sync的語法來執行非同步的函數,免除Callback的困擾。實際上他還是會透過編譯,把sync語法的Javascript改成async+callback。
Fibers
Fibers則是另外一種概念,也就是Coroutine,不過這需要編譯C++的原生程式來支援,所以只能在node.js環境中執行,單純靠Javascript是無法實做出來的,有興趣的話不妨看一看他的範例。其實在ECMA6(Harmony)或是Mozilla的Javascript1.7,就支援一個Coroutine的實作:Generator/Iterator,這個...就留到明天吧。
眾說紛紜,可以來點承諾嗎?...
基本上我只介紹了兩個Flow Control Library,就可以看到用法有差距,想想實際上還有數十種...真的需要都知道嗎?
說實話,雖然範例中的用法看起來應該還算直覺,但是幾十個Library,各有各的做法,真的會讓人很難選擇。不過目前有一個大致的方向,就是往Promise前進!前幾天Vexed大大已經有稍微介紹過jQuery以及win.js中使用的Promise了,就把Promise留到明天再來看看他是什麼吧。