.NET 4 的 Task大幅簡化多執行緒程式碼複雜度,而 4.5 推出的 async/await (參考)則讓非同步程式的寫法更精簡。(註:在博客園找到一篇從 Task、ThreadPool 談到 await 的廣泛介紹,可以一讀)
網頁開發老鳥都知道,JavaScript 端的非同步邏輯不比 C# 簡單,複雜起來會讓人寫程式寫到嘔出幾十兩鮮血。於是在前端也看到類似 Thread、Task、async/await 的發展史:最早只能靠 Callback、setTimeout 苦手硬刻,ES6 有了 Promise, 到了 ES7,async/await 也出現了。撤底搞懂 ES6 Promise、Generator,ES7 async/await 是很有成就感的事(如果你想試試的話: 1 2),不過如果你跟我一樣,要寫前端但不想走在最前端,讓 TypeScript 負責封裝背後的複雜性,輕鬆下指令也是很好的選擇。
TypeScript 早在 1.7 版就支援 async 與 await,但有個大罩門,編譯出來的 JavaScript 只能在 ES2015/ES6 目標平台執行(參考:ES 規格檢查表),連 IE11 都不支援,對必須考量 IE 相容的開發者來說,英雄無用武之地,差不多是這種感覺:
前陣子推出的 TypeScript 2.1 有個天大好消息,async / await 向下相容 ES5 目標平台,IE9 嘛A通,都到了這份兒上,還有不學不用的道理?
先來看個範例:
<%@ Page Language="C#" %>
<scriptrunat="server">
void Page_Load(object sender, EventArgs e)
{
var m = Request["m"];
if (m == "guid")
{
System.Threading.Thread.Sleep(2000);
Response.Write(Guid.NewGuid().ToString());
Response.End();
}
elseif (m == "time")
{
System.Threading.Thread.Sleep(1000);
Response.Write(DateTime.Now.ToString("HH:mm:ss"));
Response.End();
}
}
</script>
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<button>Call AJAX</button>
<div class="msg"></div>
<script src="Scripts/jquery-3.1.1.js"></script>
<script src="Scripts/promise.js"></script>
<script src="Scripts/async.js"></script>
</body>
</html>
網頁流程為按下按鈕後先呼叫 ?m=guid 由 Server 端取得 Guid,再呼叫 ?m=time 取回時間,將結果顯示在<div>上。若以 jQuery.get 實作,程式如下:
var div = $(".msg");
var button = $("button");
button.click(() => {
button.prop("disabled", true);
$.get("/Lab.aspx?m=guid").done((guid) => {
div.text(guid);
$.get("/Lab.aspx?m=time").done((time) => {
div.text(div.text() + " " + time);
button.prop("disabled", false);
});
});
});
執行結果:
接著我們用 async/await 來改寫一番:
var div = $(".msg");
var button = $("button");
async function doAjaxJob() {
button.prop("disabled", true);
await $.get("/Lab.aspx?m=guid").then(guid => div.text(guid));
await $.get("/Lab.aspx?m=time").then((time) => {
div.text(div.text() + " " + time);
button.prop("disabled", false);
});
}
button.click(doAjaxJob);
跟 C# 做法類似,function 加上 async 後,在其中便可使用 await 等待非同步程式結束再往下執行,await 函式傳回的必須是 Promise,而先前提到 jQuery 3 特意調整符合 Promise/A+ 規範的優勢也在此展現,$.get() 傳回值為 Promise 可直接搭配 await 使用,寫法簡潔易讀許多。
async 寫法在 TypeScript 2.1 以前有目標平台限制,低於 ES6(ES2015)會出現錯誤訊息。
安裝TypeScript for VS2015 2.1+版可以解除限制,編譯出相容 ES5 的程式碼。但由於 ES5 欠缺 Promise 定義,還需要使用 NuGet 安裝 es-promise 或其他包含 Promise 的 TypeScript 定義檔,才能順利編譯。
再稍稍走深一點,切換 ES 版本來觀察 TypeScript 如何在 ES5 及 ES6 實現 async / await:
TypeScript 在 ES5 實現 async、await 的祕密如下:
很可怕,不要問。不過也不需要懂(有興趣搞懂可以看這篇),有了 TypeScript 2.1, 我們放心寫 async / await 就好,就算要相容 IE9-11 也不怕,再加上先前介紹過的 Template String,教人怎能不愛它?
最後用一個美妙的 async/await 範例收尾,大家知道要怎麼在 JavaScript 實現 Thread.Delay(…) 嗎?Yes,setTimeout(),如果要做到一秒後印1s,再兩秒後印3s,再3秒後印6s,程式大約會像這樣:
var div = $(".msg");
var button = $("button");
function doAjaxJob() {
button.prop("disabled", true);
setTimeout(() => {
div.text("1s");
setTimeout(() => {
div.text("3s");
setTimeout(() => {
div.text("6s");
button.prop("disabled", false);
}, 3000);
}, 2000);
}, 1000);
}
button.click(doAjaxJob);
讓我們用 await 改寫看看:
var div = $(".msg");
var button = $("button");
async function delay(duration: number) {
returnnew Promise((resolve, reject) => {
setTimeout(resolve, duration);
});
}
async function doAjaxJob() {
button.prop("disabled", true);
await delay(1000);
div.text("1s");
await delay(2000);
div.text("3s");
await delay(3000);
div.text("6s");
}
button.click(doAjaxJob);
看到 JavaScript 可以 Thread.Sleep,程式還這麼清爽,我感動到都快哭了~ 快升級 TypeScript 2.1 感受一下吧!