关于即将到来的“演出季”以及Jscex类库
2010-11-22 20:30 by 老赵, 3942 visits又到了一年一度的“演出季”,接下来将是各式会议扑面而来的一个月。作为“与会爱好者”我自然也进入了繁忙的准备工作。接下来我将在TUP(11月27日)、2010年第二届.NET技术大会(12月4~5日)以及CSDN软件开发2.0大会(12月9~10日)上与大家分享四场演讲。不过除了一场是关于Windows并发编程的基础以外,其余三场的话题都是围绕“微软在异步编程方面的演变”。在这场演讲中,我还会引入一个与该话题密切相关的JavaScript类库:Jscex。
并发编程已经成为我们无法回避的一点,但是传统的编程语言在并发、异步等方面的支持非常有限,除了一些面向并发的编程语言(如Erlang),我们只能使用回调的方式,这样代码则被拆得支离破碎。如今的一些新语言,如F#,Scala,Go以及未来的C#都会在这方面有直接的支持。就我个人而言,用过了这些语言之后,就几乎无法回头,因为我实在无法体会到编程的快感。
如今微软的.NET平台在异步编程领域已经开始发力,从最初十分原始的Begin/End的异步模型和基于事件的异步模型,到后来F#中的异步工作流,.NET 4.0中的Observable及响应式框架,以及未来C#中将要出现的异步特性,.NET平台上涌现了各种与并发和异步编程的机制。未来几场演讲的主要话题,便是关于这些异步编程模型的演变。
只可惜,在某些和异步密切相关的平台上,例如使用JavaScript的浏览器平台,开发人员还是不得不利用最传统的开发模型。响应式框架通过引入一个“推模型”改进了这一点,不过我最欣赏的异步编程支持还是F#上的异步工作流。首先,它是一套“类库”(基于F#中的计算表达式特性);其次,它让异步编程变得像传统的顺序式开发那样简单。有天我突然意识到,JavaScript其实提供一个很强大的机制:它可以使用toString方法得到一个函数的原始代码,于是我们可以对此解析并生成新的代码,最后使用eval得到新的函数。通过这个手法,JavaScript语言几乎得到了无限的灵活性。
于是在未来的演讲中,我将会展示一个JavaScript类库的原型:Jscex(目前还未提交任何代码)。Jscex是JavaScript Computation Expressions的缩写,即“JavaScript的计算表达式”。计算表达式的原理,便是将一段顺序式的代码,重新编译成另外一个表达式,它便是F#中异步工作流的基础。
例如,它会将下面这段JavaScript函数:
function (urlA, urlB) { var reqA = $await(Jscex.Async.sendRequest(urlA, "GET")); var lengthA = reqA.responseText.length; var reqB = $await(Jscex.Async.sendRequest(urlB, "GET")); var lengthB = reqB.responseText.length; return lengthA + lengthB; }
编译成另一段代码:
function (urlA, urlB) { return $async.Delay(function() { return $async.Bind(Jscex.Async.sendRequest(urlA, "GET"), function(reqA) { var lengthA = reqA.responseText.length; return $async.Bind(Jscex.Async.sendRequest(urlB, "GET"), function(reqB) { var lengthB = reqB.responseText.length; return $async.Return(lengthA + lengthB); }); }); }); };
对于$async这个构造器来说,$await便是一个bind函数,在此处则会形成一个回调。此外,Jscex也必须能够正确处理循环操作,例如这个时钟示例(建议使用IE 9,Chrome或Firefox等支持canvas的浏览器观看)其实是这样实现的:
var drawClockAsync = eval(Jscex.compile("$async", function (interval) { while (true) { drawClock(new Date()); $await(Jscex.Async.sleep(interval)); } })); Jscex.Async.start(drawClockAsync(1000));
对于时钟来说,我们往往会不断使用window.setTimeout来更新界面。不过有了Jscex,一切都变得“顺其自然”。我们只要写一个“死循环”,在需要“等待”的时候,直接$await一个sleep操作即可。这种看似“阻塞”的代码,其实最终会被重新编译成新的函数:
function(interval) { return $async.Delay(function() { return $async.While( function() { return true; }, $async.Delay(function() { drawClock(new Date); return $async.Bind(Jscex.Async.sleep(interval), function() { return $async.Return(); }); }) ); }); };
实际上,它们都是异步的。
其实在浏览器上有太多的异步场景。例如一个AJAX请求,或者是一段动画效果。这里我还准备了第二个示例,其中有一段移动元素的方法(将元素e在duration时间内从startPos移动至endPos)是这样编写的:
var moveAsync = eval(Jscex.compile("$async", function (e, startPos, endPos, duration) { e.style.left = startPos.x; e.style.top = startPos.y; var time = 0; while (time < duration) { $await(Jscex.Async.sleep(50)); time = time + 50; e.style.left = startPos.x + (endPos.x - startPos.x) * time / duration; e.style.top = startPos.y + (endPos.y - startPos.y) * time / duration; } })); var moveBox = document.getElementById("moveBox"); Jscex.Async.start(moveAsync(moveBox, { x: 0, y: 0 }, { x: 300, y: 0 }, 1000));
没有回调,只有最普通的顺序式编程。甚至我们还可以将moveAsync方法组合到另一个方法中去:
var moveSquareAsync = eval(Jscex.compile("$async", function (e) { $await(moveAsync(e, { x: 100, y: 100 }, { x: 400, y: 100 }, 1000)); $await(moveAsync(e, { x: 400, y: 100 }, { x: 400, y: 400 }, 1000)); $await(moveAsync(e, { x: 400, y: 400 }, { x: 100, y: 400 }, 1000)); $await(moveAsync(e, { x: 100, y: 400 }, { x: 100, y: 100 }, 1000)); })); var moveSquareBox = document.getElementById("moveSquareBox"); Jscex.Async.start(moveSquareAsync(moveSquareBox));
如何让一个元素沿方形移动?“连续执行”四遍moveAsync方法即可。当然,moveSquareAsync方法最终还是由“回调”组成的:
function (e) { return $async.Delay(function () { return $async.Bind(moveAsync(e, {...}, {...}, 1000), function () { return $async.Bind(moveAsync(e, {...}, {...}, 1000), function () { return $async.Bind(moveAsync(e, {...}, {...}, 1000), function () { return $async.Bind(moveAsync(e, {...}, {...}, 1000), function () { return $async.Return(); }); }); }); }); }); };
我以前在许多地方说过,目前浏览器上的开发效率已经受限于JavaScript的语言特性了。不过,正如我刚才所提到的那样,JavaScript语言的灵活性几乎是无穷的。我认为,Jscex会是一个突破。当然,现在您看到的演示只不过是个原型,如果要提供一个完整的解决方案还有太多的路要走。例如现在的Jscex还不支持for,if,try...catch等常见语言特性,代码也丑陋得很。与此对应的Jscex.Async也只是个初步的模型,且只有寥寥无几的辅助方法。
我勒个去!Awesome!
你参加今年 D2 吧,我等着哦~