iT邦幫忙

2

使用Flow Control Library來解決非同步的流程問題

fillano 4 年前6153 瀏覽

基本上Flow Control並不是很困難的概念,加上伺服器端Javascript越來越多人使用,為了解決非同步時的Flow Control問題,許多Library就如雨後春筍般冒出...
接續上一篇:Javascript中非同步執行的一些問題,今天來看看到底有哪些Library,挑選幾個來看看到底要怎麼用...

因為使用的人多,node.js網站上有一個Flow Control的模組列表:
https://github.com/joyent/node/wiki/Modules#wiki-async-flow

上面洋洋灑灑幾十個,這要怎麼選阿Orz

為了方便,先把這些大致分類一下:

  1. 傳統做法:比較類似我之前介紹的做法,
  2. 改變Javascript語法:主要是為了可以用同步的方式使用非同步的函數,需要編譯Javascript
  3. Fibers:用C++原生程式加上Thread實作Coroutine

接下來會簡單示範幾個使用傳統做法的例子,包括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是無法實做出來的XD,有興趣的話不妨看一看他的範例。其實在ECMA6(Harmony)或是Mozilla的Javascript1.7,就支援一個Coroutine的實作:Generator/Iterator,這個...就留到明天吧。

眾說紛紜,可以來點承諾嗎?...

基本上我只介紹了兩個Flow Control Library,就可以看到用法有差距,想想實際上還有數十種...真的需要都知道嗎?

說實話,雖然範例中的用法看起來應該還算直覺,但是幾十個Library,各有各的做法,真的會讓人很難選擇。不過目前有一個大致的方向,就是往Promise前進!前幾天Vexed大大已經有稍微介紹過jQuery以及win.js中使用的Promise了,就把Promise留到明天再來看看他是什麼吧。


1 則留言

0
ted99tw
iT邦研究生 1 級 ‧ 4 年前

JS把戲真不少,好像在桃花源內看魔術...灑花

我要留言

立即登入留言