iT邦幫忙

1

Javascript Flow Control的承諾與未來

fillano 3 年前5087 瀏覽

今天先來介紹一下Promise,然後再看看未來在Flow Control相關領域的發展狀況或可能性
Promise

其實Promise可能還有幾種不同的名稱,包含Deferred、Future等,不過Javascript標準組織CommonJS已經有Promise的提案,所以就把它統稱為Promise吧。

從Wiki上面關於Future and promises的條目可以知道,其實很早就提出這個概念...

在Javascript中,可以把Promise視為一個物件,他有三個狀態:unfulfill、fulfilled(resolved)、fail(rejected),當一個Promise物件初生成時,是在unfullfil狀態,之後他的狀態可以轉變為fulfilled(resolved)或是fail(rejected),但是狀態轉變為fulfilled(resolved)或是failed(rejected)後,他的狀態就不會再有變化。

之前看到的一些Flow Control,基本上都是用函數執行計數的方式在做執行狀態的控管。在Promise內部,則會傳遞Promise物件,只有在Promise的狀態到達fulfilled(resolved)或是failed(rejected)時,才進入下一步,並且把傳遞的訊息傳給下一步。

之前Vexed大大已經有做過jQuery以及win.js裡面實作的Promise的比較,我接下來的例子,則會使用Q這個Library。在這之前,先介紹一下基本的API。

目前主要提供Promise機制的Library,大概都跟CommonJS Promise/A+相容,所以等一下就盡量只使用這個規格提案中的API。

除了本身的狀態,Promise還會提供:then(onResolved, onRejected):當Promise的狀態改變,就會呼叫相對應的處理函數。

由於只有Promise提供then的功能,在使用Promise時,至少需要返回一個Promise物件,才有辦法使用then,通常有兩種方法可以開始:

  1. 呼叫Q.fcall(function(){}):只要把要使用的函數傳給他,fcall就會返回一個promise物件,這樣才能呼叫then
Q.fcall(function(){
    return 1;
})
.then(onresolved, onrejected);//console顯示1
Q.fcall(function() {
    throw "new error.";
})
.then(onresolved, onrejected);//console顯示警告:new error
function onresolved(m) {
    console.log(m)
}
function onrejected(m) {
    console.warn(m);
}
  1. 使用Q.defer()產生一個defferred物件,然後返回他的promise成員
(function() {
    var deferred = Q.defer();
    setTimeout(function() {defer.resolved(1);}, 1000);
    return deferred.promise;
})()
.then(onresolved, onrejected);//一秒鐘後在console顯示1

(function() {
    var deferred = Q.defer();
    setTimeout(function() {defer.reject('new error');}, 1000);
    return deferred.promise;
})()
.then(onresolved, onrejected);//一秒鐘後在console顯示警告:new error

function onresolved(m) {
    console.log(m)
}
function onrejected(m) {
    console.warn(m);
}

使用deferred.resolve(),可以把它的promise成員狀態改為fulfilled(resolved),使用deferred.reject(),則會把他的成員promise狀態改為fail(rejected)。簡單的使用方法就像這樣。再來把昨天的例子用Promise改寫:

<script src='http://code.jquery.com/jquery-1.9.1.min.js'></script>
<script src='https://raw.github.com/kriskowal/q/master/q.min.js'></script>


<button id='btn1'>start promise</button>
<div id='panel'></div>
<script>
$(function() {
     $('#btn1').click(function() {
          Q.all(function() {//這裡需要回傳promise陣列,所以使用Q.all
               var d1 = Q.defer();
               var d2 = Q.defer();
               $.get('test877a.txt', function(data, status) {
                    if(status==='success') {
                         d1.resolve(data);
                    } else {
                         d1.reject('ajax error');
                    }
               });
               $.get('test877b.txt', function(data, status) {
                    if(status==='success') {
                         d2.resolve(data);
                    } else {
                         d2.reject('ajax error');
                    }
               });
               return [d1.promise, d2.promise];
          }())//Q.all接收的是promise陣列不是函數,所以這裡直接執行
          .then(
               function(texts){
                    return texts.map(function(text) {
                         return text.toUpperCase();
                    });
               },
               function(m){console.warn(m)}
          )
          .then(
               function(texts){
                    return texts.map(function(text) {
                         return '[['+text+']]';
                    });
               },
               function(m){console.warn(m)}
          )
          .done(
               function(texts) {
                    $('#panel').html(texts.join('<br>'));
               },
               function(m){console.warn(m)}
          );
     });
});
</script>

處理非同步的狀況,需要同時等待多個Promise時需要注意,每一個Promise的狀態都需要是fulfilled(resolved)或是fail(rejected)時,才會進入下一步。這裡常常會出問題,例如$.get只接收data而沒有處理textStatus,萬一url給錯或伺服器有問題,就可能不會進入下一步,也不會提示錯誤。所以自己產生promise時,一定要讓promise物件走到這兩個狀態。

網路上討論及介紹Promise的文章很多,google一下應該就可以找到很多參考資料。

Future?

其實這裡不是要介紹Future...而是看看Flow Control的未來的其他可能性。

ECMA-262 Edition 6 (aka Harmony,不過他的歷史很火爆)預計在今年年底完成,裡面有一個新的功能,叫做Generator/Iterator。用程式來解釋會比較清楚:

var a = function() {
    for(var i=0; i<10; i++) {
        yield i;
    }
}();
console.log(a.next());//顯示0
console.log(a.next());//顯示1
console.log(a.next());//顯示2
console.log(a.next());//顯示3
console.log(a.next());//顯示4
console.log(a.next());//顯示5
console.log(a.next());//顯示6
console.log(a.next());//顯示7
console.log(a.next());//顯示8
console.log(a.next());//顯示9
console.log(a.next());//出錯

簡單地說,函數執行到yield時,會中斷執行並返回一個Iterator,每次呼叫next()時,會返回yield回傳的結果,然後函數繼續執行到碰到下一個yield。透過send(),可以把資訊傳給函數,例如:

var a = function() {
     var i;
     for(i=0; i<10; i++) {
          if(typeof r !== 'undefined') {
               i = r;
          }
          var r = yield i;
     }
}();
console.log(a.next());//顯示0
console.log(a.next());//顯示1
console.log(a.next());//顯示2
console.log(a.next());//顯示3
console.log(a.next());//顯示4
console.log(a.next());//顯示5
console.log(a.next());//顯示6
console.log(a.next());//顯示7
console.log(a.next());//顯示8
console.log(a.send(0));//顯示0
console.log(a.next());//顯示1

這裡的demo目前只能在Firefox執行,需要在script tag加上type="application/javascript;version=1.7"。V8目前還在努力中,就等看他什麼時候出來。

另外ECMA6的Generator,跟Mozilla Javascript 1.7的不太一樣,必須在Generator function加上*,例如:

var a = function*() {
........
}():
........

Generator到底有什麼用處?簡單地說,這是產生Iterator的好方法。常看到的例子是費氏數列,利用Generator可以產生一個無限的費氏數列Iterator,只要一直next(),就可以取得數列的下一個值。這樣就不用一次把所有結果算出來,只要計算每次需要的結果,所以也可以節省一些計算資源。

Node.js的Fibers模組,也提供了類似功能的支援,也可以用它來試試yield能做什麼...其實看起來比Javascript的Generator還強XD。例如sleep()的範例,使用Javascript的Generator有點難寫的出來...而且跑起來也沒相同效果...

<script type="application/javascript;version=1.7">
console.log(sleep(1000));//不用console.log不會動XD
function sleep(ms) {
	var t1 = new Date().getTime();
	var a = function () {
		setTimeout(function() {
			console.log(new Date().getTime()-t1);
			a.close();
		}, ms);
		yield;
	}();
	return a;
}
console.log('here');
</script>

嚴格說來,Generator並不是Flow Control,他只是會影響函數的執行流程就是了。

Q與未來的承諾

如果有看過ECMA-262 Strawman:Concurrency就會發現,Promise有可能在未來成為Javascript的內建標準物件,而且目前提案的作者(Mark S. Miller, Caja的設計者),似乎就把Q拿來改一改...在Q的github wiki上,Q的作者Kris Kowal有做了一個比較:Comparing ES Harmony Concurrency Strawman。參考這兩份文章,也許可以一窺Promise在Javascript的未來。(但是未來很難說XD

就目前態勢看起來,Promise有機會成為在Javascript中做Flow Control的標準方法,這樣就可以從數十種Library中解脫,專注在Promise上囉。


尚未有邦友留言

立即登入留言