技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 算法 --> 受禁锢的异步编程思维

受禁锢的异步编程思维

浏览:1658次  出处信息

    最近一直在努力推广Jscex,补充了很多中文文档和示例,因此博客上都已经有两篇文章有了“上”而没有“下”,即使最复杂的图示也已经绘制完毕。在推广Jscex的过程中,我发现有个比较明显的问题是,许多使用JavaScript的程序员已经习惯旧有的编程方式,甚至推崇一些据他们说很“漂亮”的模式。但在我看来,这其实跟许多GoF模式是在修补OO语言的不足有些类似,很多异步模式都只是因为JavaScript语言特性不足而设计出来的“权宜之计”。我们在传统JavaScript编程环境下并没有其他选择,单纯地认为这是“美”,说实话只不过是一种安慰罢了。

    Jscex的重头戏便是处理异步操作,但异步操作并不只是如Node.js中通过回调函数传回结果的那些方法,或者是网页上的AJAX请求等等。异步操作的定义其实可以概括成“会在未来某个时刻完成的操作”,就只是这么简单。什么事情会在未来发生,那么它对你来说就是个异步操作。因此其实在日常开发过程中可谓到处是异步操作,例如:

  • 播放动画(播放会在未来结束)
  • 模态对话框关闭(模态对话框会在未来关闭)
  • 用户操作(用户会在未来点击按钮)
  • 各类事件(数据流会在未来关闭,WebWorker会在未来获得消息,图片会在未来加载成功)
  •     这些示例实在数不胜数。但是,在许多JavaScript程序员眼中,似乎只有AJAX或是Node.js中的那些异步方法才算是异步操作。其他的东西,比如用户点击一个按钮,这难道不是个天然的“事件”吗?其实这就要视这个异步任务的性质如何了。如果它是一系列操作的“发起者”,那么的确,使用事件触发的方式来对待这次点击操作可能是最合理的。但如果,这个操作只是一系列过程中的一个步骤,那么如果依然把它视为一个事件型的操作,就只会破坏我们的逻辑了。

        举个例子,和Jscex的快速入门比较类似,即菲薄纳契(Fibonacci)数列

        其边界情况为:

        以上是其标准定义,直接写成算法即是:

    var fib = function () {
    
        console.log(0);
        console.log(1);
    
        var a = 0, current = 1;
        while (true) {
            var b = a;
            a = current;
            current = a + b;
    
            console.log(current);
        }
    };

        上述代码将会无限地循环下去,不断输出数列的每一项。快速入门里的要求,是将其修改为“每隔一秒输出一个数字”,于是有同学就说:这不天生是计时器的场景吗?但事实并非如此。“计时器”或是setTimeout函数,都只是环境提供给我们的唯一可用的功能,我们要意识到这不是我们主动的“选择”。如果一看到“每隔一秒”这样的需求,JavaScript程序员就认为“计时器”是“最好”的办法,这就说明思维被禁锢了。我相信这样的功能交给其他任何平台的程序员,他们的第一感觉几乎都会是“使用Sleep函数暂停一秒”。这其实才是最简单的做法,直接,清晰,完整保留现有代码逻辑。

        这也是基于Jscex之后的实现方式。这里我再将要求修改一下,改为用户“每点击一次按钮”输出一个数字,又该怎么做?基于Jscex的做法如下:

    var Async = Jscex.Async;
    
    var fibAsync = eval(Jscex.compile("async", function () {
    
        var button = document.getElementById("button");
    
        $await(Async.onEvent(button, "click")); // 等待用户点击
        console.log(0);
    
        $await(Async.onEvent(button, "click")); // 等待用户点击
        console.log(1);
    
        var a = 0, current = 1;
        while (true) {
            var b = a;
            a = current;
            current = a + b;
    
            $await(Async.onEvent(button, "click")); // 等待用户点击
            console.log(current);
        }
    }));
    
    fibAsync().start();

        有朋友可能会问:用户点击按钮不是需要响应事件的嘛,这个事件到哪里去了?其实正像我所说的那样,把这里的“用户点击按钮”当作事件对待并非最合理的方式,因为它只是“整个过程”中的一个环节而已。在这里,我们其实只是要在输出数字之前“等待用户点击”即可,这个“输出”以及相关的“计算”操作,并非是由“按钮点击”所触发的逻辑,而是一个连续的统一过程中的一部分而已。

        您可以试试纯粹使用事件机制来实现这个功能,保证您需要重新实现这段斐波那契数列的逻辑。当然,菲薄纳契数列的逻辑很简单,重写下估计也不会花太大的功夫,但如果您需要改造汉诺塔的动画效果呢?

    var hanoiAsync = eval(Jscex.compile("async", function (n, from, to, mid) {
        if (n > 0) {
            $await(hanoiAsync(n - 1, from, mid, to));
        }
    
        // 等待按钮点击
        // var btnNext = document.getElementById("btnNext");
        // $await(Jscex.Async.onEvent(btnNext, "click"));
    
        $await(moveDishAsync(n, from, to));
    
        if (n > 0) {
            $await(hanoiAsync(n - 1, mid, to, from));
        }
    }));

        以上代码是以动画形式表现汉诺塔的解题过程,但如果用户提出想要“每点一次按钮”才移动一个盘子,那其实我们只要将上面两行代码取消注释即可。如果忽然有一天,老板要求通过一个选项来决定是否“自动移动”,在Jscex里只要加一个if判断即可。您可以简单设想一下直接裸写这些代码会遇到什么样的景象,改造时会遇到哪些困难。

        我还为Jscex准备了一个示例,是关于“模态对话框”配合相关异步操作的。由于是“模态对话框”,我们是要在对话框关闭之后才继续做某些事情。可惜在JavaScript中,如果您直接把一个界面元素展现为一个模态对话框,它是无法阻止后面的代码继续执行的,要阻止则只能使用confirm或alert方法。于是,我们只能把后续操作放到一个回调函数中去,并在模态对话框关闭之后才执行。但是您要知道,模态对话框只不过是整个过程中的一个步骤,理想状况下我们的完整功能不该被拆成多个部分,再使用所谓“美妙”的回调串联起来。

        这点在Jscex中还是那么简单,直接按最简单的逻辑来进行即可:

    // 显示模态对话框
    $await($("#dialog-confirm").dialogAsync({ modal: true }));
    
    // 发起AJAX请求
    var response = $await($.ajaxAsync({ url: "...", dataType: "text" }));
    
    // 继续做事

        而无需:

    // 继续做事
    $("#dialog-confirm").dialog({
        modal: true,
        close: function () {
            // 发起AJAX请求
            $.ajax({
                url: "...",
                dataType: "text",
                success: function () {
                    // 继续做事
                }
            });
    });

        经常会听到有些朋友谈起,说在实际开发过程中很少遇到异步场景。但在我看来,实在可谓遍地是异步,这种观念的差别只是在于是否经过了“抽象”。不加抽象地使用技术平台为我们提供的异步操作,会让我们的思维被它所禁锢。在JavaScript编程中浸淫太久了,可能就会忘记我们从最初是如何编程的。Jscex的目标,便是将这些东西回归自然,将逻辑以最自然的方式表达出来。循环?那就用for或是while吧,在函数之间跳来跳去是做什么的?

        我从来不担心的Jscex的实用价值。Jscex来自C#,F#以及Scala等现成的理念,各种开发模式都是被翻来覆去讨论过,总结过,验证过的。这些语言其实都能实现与JavaScript类似的编程模式,但它们不需要,因为语言特性让程序员可以使用更简单直接的做法来解决问题。Jscex只是将这些现成的内容,从其他模式带到JavaScript编程领域上而已。

        如今我唯一担心的,只是那些被禁锢的编程思维。

    建议继续学习:

    1. 关于IO的同步,异步,阻塞,非阻塞    (阅读:14482)
    2. fsockopen 异步处理    (阅读:9030)
    3. 配合jquery实现异步加载页面元素    (阅读:5375)
    4. 使用django+celery+RabbitMQ实现异步执行    (阅读:5019)
    5. 异步编程与响应式框架    (阅读:3911)
    6. 多核与异步并行    (阅读:3892)
    7. redis源代码分析 - event library    (阅读:3175)
    8. Google Analytics 异步代码详解    (阅读:3163)
    9. 异步完成后新开窗口    (阅读:2951)
    10. php的异步http请求类    (阅读:2919)
    QQ技术交流群:445447336,欢迎加入!
    扫一扫订阅我的微信号:IT技术博客大学习
    << 前一篇:累积发送模式
    © 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

    京ICP备15002552号-1