Hello World
Spiga

关于即将到来的“演出季”以及Jscex类库

2010-11-22 20:30 by 老赵, 2658 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也只是个初步的模型,且只有寥寥无几的辅助方法。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

26 条回复

  1. infinte
    218.104.71.*
    链接

    infinte 2010-11-22 20:40:51

    我勒个去!Awesome!

    你参加今年 D2 吧,我等着哦~

  2. 老赵
    admin
    链接

    老赵 2010-11-22 20:48:16

    @infinte

    报名了,但被拒鸟……

  3. 闲耘™
    121.0.29.*
    链接

    闲耘™ 2010-11-23 09:16:30

    拒绝!!! 什么世道啊。

  4. waynebaby
    112.64.188.*
    链接

    waynebaby 2010-11-23 09:35:36

    nnd .net 技术大会2 我竟然不知道。。。

  5. waynebaby
    210.22.108.*
    链接

    waynebaby 2010-11-23 10:30:23

    TUP 这个是现场交钱的? 没找到付费地址啊

  6. d2forum.org
    121.0.29.*
    链接

    d2forum.org 2010-11-23 10:38:01

    @老赵:

    我们在作品秀报名页面指定的邮箱中没有发现您的报名信息,不知道何来拒绝一说?如果您有兴趣参加D2作品秀,热烈欢迎报名哈。

  7. 怿飞
    121.0.29.*
    链接

    怿飞 2010-11-23 10:47:25

    欢迎报名,另外本届D2论坛报名时间到11月30日才截止,邀请估计到12月3日发送

  8. 老赵
    admin
    链接

    老赵 2010-11-23 11:08:41

    @waynebaby

    不用交钱吧?

  9. 老赵
    admin
    链接

    老赵 2010-11-23 11:10:14

    @d2forum.org

    嗯?我当时是让公司关心会务的同事帮忙联系的,说是你们答复说必须是实际生产中的案例。

    那么我一会儿也去申请一下,呵呵。

  10. avlee
    119.62.29.*
    链接

    avlee 2010-11-23 15:18:26

    漂亮,看了演讲再讨论

  11. meback
    114.255.238.*
    链接

    meback 2010-11-24 09:59:09

    赵哥哥,Google Reader订阅不到你的更新,看看为什么吧:(

  12. 链接

    小城故事 2010-11-24 23:13:55

    昨晚Server挂掉了,今天试下

  13. winter
    218.82.134.*
    链接

    winter 2010-11-28 00:13:45

    D2的同学们 你们到底在搞什么? 怎么反反复复的?

  14. 裕波
    218.82.178.*
    链接

    裕波 2010-11-28 00:20:29

    关于这个主题当初是由我和D2联系的,本想说邀请老赵去D2上做分享,后来以这个内容没有实际项目运用拒绝了。

  15. winter
    218.82.134.*
    链接

    winter 2010-11-28 00:23:56

    好吧 我终于明白作品秀和分享的区别了

    5 分钟演示 +5 分钟交流 一共10分钟 大家去听吧 保证过瘾

  16. 老赵
    admin
    链接

    老赵 2010-11-28 01:16:28

    @winter

    要演示起来我肯定不止10分钟的,我觉得,至少15到20分钟,哈哈。

    对了,你去D2吗?

  17. winter
    114.80.133.*
    链接

    winter 2010-11-29 13:39:17

    @老赵

    本来挺想去听听 8过淘宝滴同学们这个态度让我觉得很无趣啊

    没有实际项目运用这回事......

  18. 老赵
    admin
    链接

    老赵 2010-11-29 15:14:47

    @winter

    估计有决定权的淘宝同学不是我们平时遇到的那些,而是高年级同学……话说我这个东西周三欢迎来内部分享会听,很带劲儿的哟……

  19. winter
    114.80.133.*
    链接

    winter 2010-11-30 14:09:23

    @老赵

    一定一定 JS的分享我怎么能不去拍砖呢 hohoho

  20. winter
    114.80.133.*
    链接

    winter 2010-11-30 14:11:15

    @老赵

    下回Webrebuild或者w3ctech上来讲一下吧

  21. diyism
    116.225.227.*
    链接

    diyism 2010-11-30 23:37:29

    function的toString有个常见的地方:

    类似php里heredoc那样的多行无转义字串: ie下:

    var ie_multi_str=function(){/*
    ...
    <input type="submit" value="Say">
    ....
    */}.toString().replace(/function\(\)\{\/\*/, '').replace(/\*\/\}/, '');
    alert(ie_multi_str);
    

    firefox下:

    var ff_multi_str=''+(<><![CDATA[
    ...
    <input type="submit" value="Say">
    ....
    ]]></>);
    
    alert(ff_multi_str);
    

    似乎没有办法兼容ie和firefox 大家有通用的办法吗?

  22. 老赵
    admin
    链接

    老赵 2010-12-01 00:15:01

    @diyism

    没看懂这段代码……不过一些特殊的地方实在做不到一致也没关系,最多把它们提取到被compile方法的外部就行了。

  23. Gsanidt
    123.138.30.*
    链接

    Gsanidt 2010-12-08 22:20:12

    沙发........

  24. 链接

    beyondjay 2011-01-03 15:59:40

    异步是大趋势

  25. zxms
    116.26.103.*
    链接

    zxms 2011-05-21 21:09:10

    w3ctech上来讲一下吧

  26. 老赵
    admin
    链接

    老赵 2011-05-21 21:11:42

    @zxms

    没问题哪。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我