受禁锢的异步编程思维

标签: 语言编程 前端表现 | 发表时间:2011-12-19 21:49 | 作者:[email protected] (老赵)
出处:http://blog.zhaojie.me/

最近一直在努力推广 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编程领域上而已。

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

相关 [禁锢 异步 编程] 推荐:

受禁锢的异步编程思维

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

Javascript异步编程的4种方法

- - 阮一峰的网络日志
你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务. 如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推. 这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.

异步编程语言的常见坑

- - idea's blog
天生支持异步编程的语言如 NodeJS, Golang 等, 创建一个异步 routine 的成本非常小, 这确实是一个非常方便的功能. 比如用在网络爬虫程序的开发, 对于每一个要抓取的 URL 就启动一个 routine, 类似启动一个线程, 既能充分利用 CPU 多核, 代码也很简洁.. 正因为太方便, 所以常常被滥用, 并引发许多严重坑.

Java 异步编程最佳实践

- - 鸟窝
最近异步编程非常流行, 主要是它能够在多核系统上提高吞吐率. 异步编程是一种编程方式,可以提高对UI的快速响应. Java中的异步编程模型提供了一致性的编程模型, 可以用来在程序中支持异步. 本文讨论了在使用Java执行异步操作应该遵循的最佳实践. 原文: Best Practices of Asynchronous Programming With Java.

演出季上“异步编程模型的演变”幻灯片

- 王雪松 - 老赵点滴 - 追求编程之美
演出季终于过去了,现在就来做一个收尾吧. 这次的主题是“异步编程模型的演变”,主要回顾了微软在.NET平台上异步编程上的进化:基于回调,基于迭代生成器,基于类库,基于语言. 不过这样的编程模型其实并非微软独有,而是一些运用比较广泛的异步编程方式,因此在SD 2.0大会上我其实完全用JavaScript进行演示.

异步编程 In .NET - 腾飞(Jesse) - 博客园

- -
async和await的前世今生的文章之后,大家似乎在async和await提高网站处理能力方面还有一些疑问,博客园本身也做了不少的尝试. 今天我们再来回答一下这个问题,同时我们会做一个async和await在WinForm中的尝试,并且对比在4.5之前的异步编程模式APM/EAP和async/await的区别,最后我们还会探讨在不同线程之间交互的问题.

如何优雅地实现"异步"编程?

- - 掘金 架构
Java异步编程极大的节省了主程序执行时间,提升了计算资源利用效率,是Java高级工程师的必备技能之一. 本文围绕什么是异步,异步解决了什么问题,怎么 异步编程来展开. 在解释异步编程之前,我们先来看 同步编程的定义. 同步编程,即是一种典型的请求-响应模型,当请求调用一个函数或方法后,需等待其响应返回,然后执行后续代码.

从事件驱动到observable的异步编程——PubSub+Promise+Rx的JS事件库

- Kejun - YY in Limbo 混沌海狂想
你上当叻,虽然从外面看标题很有气势,传达出一种宏大叙事的赶脚,其实我只是刚刚把一个阿尔法城的JS模块提交到github,想顺便介绍一下,但我连API文档都懒得写,就别指望能深入浅出的讲一遍来龙去脉了⋯⋯. 所以就直接帖几个前置阅读的链接罢. 这些潮流的外部起源:(技术也有外源论/exogenesis⋯⋯).

linux异步IO浅析

- Sepher - kouu's home
知道异步IO已经很久了,但是直到最近,才真正用它来解决一下实际问题(在一个CPU密集型的应用中,有一些需要处理的数据可能放在磁盘上. 预先知道这些数据的位置,所以预先发起异步IO读请求. 等到真正需要用到这些数据的时候,再等待异步IO完成. 使用了异步IO,在发起IO请求到实际使用数据这段时间内,程序还可以继续做其他事情).

Android handler异步更新

- - 博客园_首页
private static final int MSG_SUCCESS = 0;// 获取图片成功的标识. private static final int MSG_FAILURE = 1;// 获取图片失败的标识. mImageView.setImageBitmap((Bitmap) msg.obj);// imageview显示从网络获取到的logo.