[Javascript] 使用Jscex讓Javascript程式碼更具可讀性

摘要:[Javascript] 使用Jscex讓Javascript程式碼更具可讀性

前言

隨著Node.js的出現,加上Windows 8也支援使用HTML和JavaScript開發桌面應用程式,

這一年來關於JavaScript的各種library和應用更是如雨後春筍般不斷的冒出,

身為一個程式開發人員面對JavaScript的機會也越來越多,

而在我們使用JavaScript進行開發的時候,最常遇到的應該是Async模型,

或是具有Callback的函數,而當在程式碼中大量使用這些具有Callback的函數時,

很容易造成程式碼趨於複雜,而導致難以閱讀和維護,

Jscex正好可以很好的解決我們所遇到的問題。

Jscex老趙所寫的一套Open Source Javascript Library,採用BSD授權。

實際演練

舉一個我們最常見具有Callback的Javascript Function,JQuery的$.ajax,

它的定義如下:

 $.ajax({
    url: 'your_api_url',
    error: function(xhr) {
      // Something when error occurs      
    },
    success: function(response) {
      // Something when success
    }
  });

假設我們今天需要使用JavaScript撰寫以下的邏輯

  1. 我們具有一個WebApi,可用來計算兩個int的總和
  2. 現在有四個變數a = 1, b = 2, c = 3, d = 4
  3. 變數依序兩個一組透過Api計算之後的結果,再與下一個變數繼續進行計算,直到所有變數計算完畢
    (Ex. var result = a + b; result = result + c; result = result + d; )
  4. 最後將結果顯示在畫面上

那麼我們透過Ajax的寫法很可能就會寫成以下這樣:

點我看完整範例

$.ajax({
    url: '/WebApi/CalculateService.svc',
    type: 'GET',
    data: { a: a, b: b },
    dataType: "json",
    success: function (response1) {
        $.ajax({
            url: '/WebApi/CalculateService.svc',
            type: 'GET',
            data: { a: response1.sum, b: c },
            dataType: "json",
            success: function (response2) {
                $.ajax({
                    url: '/WebApi/CalculateService.svc',
                    type: 'GET',
                    data: { a: response2.sum, b: d },
                    dataType: "json",
                    success: function (response3) {
                        console.log($("#result"));
                        $("#result").text(response3.sum);
                    }
                });
            }
        });
    }
});​

今天只有四個變數,為了在Callback Function之中繼續進行計算,就已經是三層的巢狀Function,

如果今天我有10個變數,基本上這代碼已經不具有可讀性...

如果我使用Jscex來改寫這段程式碼,它會長的像這樣

點我看完整範例

var main = eval(Jscex.compile("async", function () {
    var result = 0;
    result = $await(calculate(a, b));    
    result = $await(calculate(result, c));
    result = $await(calculate(result, d));

    $("#result").text(result);
}));

main().start();

是不是突然覺得

世界變得美好多了呢?程式碼變得清楚多了呢?

Jscex參考了C# Task Parallel Library和Asynchronous Programming,以及F# Asynchronous的優點,

來讓程式碼透過加上語義詞,以及使用Task的概念,

來讓非同步的程式碼可以以同步的方式撰寫,大大增加了程式碼的可讀性,

像在上面的程式碼中 eval(Jscex.compile("async", function) 回傳的是一個Task對象,

所以我需要在最後加上main().start()來讓Task開始運行 (其實有點像是Task Parallel Library)

而上面的程式碼其實我還將ajax的部分利用Jscex做了一些包裝,來讓程式碼使用上更方便,如下:

var Task = Jscex.Async.Task;
$.ajaxAsync = function (options) {
    return Task.create(function (t) {

        options.success = function (data, textStatus, jqXHR) {
            t.complete("success", {
                data: data,
                textStatus: textStatus,
                jqXHR: jqXHR
            });
        }

        options.error = function (jqXHR, textStatus, errorThrow) {
            t.complete("failure", {
                jqXHR: jqXHR,
                textStatus: textStatus,
                errorThrow: errorThrow
            });
        };

        $.ajax(options);
    });
};

此段參考了老趙的Jscex Sample

var calculate = eval(Jscex.compile("async", function (a, b) {
    var result = $await($.ajaxAsync({
        url: '/WebApi/CalculateService.svc',
        type: 'GET',
        data: { a: a, b: b },
        dataType: "json"
    }));

    return result.data.sum;
}));

利用Jscex將原本具有Callback的函數,改寫為同步的方式,

不但容易閱讀,也讓程式碼的維護更為輕鬆了!

在後面的文章之中,會再補充說明詳細的寫法。

效能? 安全性?

我想很多人在上面的程式碼之中,看到了一個關鍵字eval,

這是因為Jscex它會動態的compile你的JavaScript程式碼,編譯成原生的JavaScript程式碼,

在執行上面的程式時,我們可以透過瀏覽器的Console視窗看到以下資訊

// Original: 

function () {
    var result = 0;
    result = $await(calculate(a, b));
    result = $await(calculate(result, c));
    result = $await(calculate(result, d));

    $("#result").text(result);
}



// Jscexified: 

/* async << function () { */               (function () {
                                               var _builder_$0 = Jscex.builders["async"];
                                               return _builder_$0.Start(this,
                                                   _builder_$0.Delay(function () {
/*     var result = 0; */                              var result = 0;
/*     var _result_$ = $await(calculate(a, b)); */     return _builder_$0.Bind(calculate(a, b), function (_result_$) {
/*     result = _result_$; */                              result = _result_$;
/*     var _result_$ = $await(calculate(result, c)); */    return _builder_$0.Bind(calculate(result, c), function (_result_$) {
/*     result = _result_$; */                                  result = _result_$;
/*     var _result_$ = $await(calculate(result, d)); */        return _builder_$0.Bind(calculate(result, d), function (_result_$) {
/*     result = _result_$; */                                      result = _result_$;
/*     $("#result").text(result); */                               $("#result").text(result);
                                                                   return _builder_$0.Normal();
                                                               });
                                                           });
                                                       });
                                                   })
                                               );
/* } */                                    })

其實在瀏覽器真正eval的是Jscexified後的程式碼 (也就是下面這段)

這時候可能又會有人問說,如果讓瀏覽器動態的即時compile程式碼,是不是會造成很大的效能問題呢?

而通常上面這段程式碼,只會出現在開發環境之中,

因為在測試無誤上線之前,我們只需要透過node.js將程式碼完全編譯成非同步的程式碼即可,

執行以下command,即可無痛轉換為原生的JavaScript程式碼

		node scripts/jscexc.js --input inputfile --output outputfile

結論

透過上面的例子,你感受到Jscex所帶來的魔力了嗎?

非同步的程式碼通常都是最難讀懂並維護的,也因此C#在4.0之後也在這部分花了很大的心思作改進,

Jscex讓JavaScript的非同步程式碼不在那麼可怕,也提高了程式的可讀性,

我想這就是它的魅力所在,未來還會再陸陸續續的詳細介紹Jscex的實際使用方法,

這邊文章希望可以讓大家知道有這樣的東西存在,或許可以讓JavaScript的開發更加的平易近人,

希望對大家有幫助,也歡迎大家多多指教或一起討論 ^_^