瘦人说

二手货—加强Mr.Async

之前发过一条微博,“看得越多,想得越多,感觉差距就越大。如果我来写Jscex将会是啥样?”。这次的Mr.Async加强的就是类似于Jscex中$await方式开发异步操作。本来这篇文章的名字叫《二手货》,主要说明了这次加强完全是copy老赵的思想,在没真正理解他的想法之前,我曾经以为我的Mr.Async已经足够强大了,囧。我们平时接触的东西都是“二手的”,这种说法即便有些看轻自己,但我暂时用这说法来促使自己多去思考东西而不是用“明星都是从模仿开始的”这种来安慰自己。

这次的加强主要包含如下几个部分。

Mr.Async.Interpreter

Mr.Async.Interpreter.js 主要用于根据已有代码生成异步代码。其中包括这么两个部分:

  1. ExpressionVisitor.js 表达式访问器,类似C#中ExpressionVisitor类,表达式的生成由UglifyJS中的解释器来完成。此类可以单独使用,通过它的extend方法创建出扩展出的子类对象。下文的Mr.Recoder.js就是一个子类对象,使用方式详见它的代码。
  2. Mr.Recoder.js ExpressionVisitor的一个子类对象,它是包含了核心生成算法,其实算法说容易也容易,就是把表达式分析之后生成Mr.Async库中Mr.asynIterator和Mr.when方式的代码。说难就难在各种语句调用方式的处理,this作用域问题等等。
  3. parser.js UgilifyJS中JavaScript解析器,生成表达式树,库中的版本可以直接引用。如果需要引用更高版本的话,需要做点点改动,详细见:改动

如果需要使用Mr.Async.Interpreter的话需要引入四个部分,如Testing用例文件,在NodeJS中只用引用Mr.Async.Interpreter.js就可以

  1. Recoder/ExpressionVisitor.js
  2. Recoder/parser.js
  3. Recoder/Mr.Recoder.js
  4. Mr.Async.Interpreter.js

引用中路径自己调整。在代码中使用Mr.Async的recode方法生成异步代码。

eval(Mr.Async.recode(function(){
    var i = $await(delay()); // 等待
    console.log(i);
}));

如果觉得eval很邪恶的话,请期待AOT编译。

$await关键字

在下一个.NET版本中,await关键字用于等待一个异步操作,并且还可以接受返回值。老赵的Jecex的起源也是想让js拥有这种优雅的编程方式。没有用过Jecex,貌似在它的$await函数是传入一个实现了Task接口的对象(猜测)。说说我的吧,我的既然是原有Mr.Async库的加强,等待的参数是一个或者多个Mr.Deferred对象。

// 一个实现了Mr.Deferred的通用异步方法
// 等待一秒钟,计算一个0到1的随机数
function asyncRandom(callback){
    var de = Mr.Deferred();
    setTimeout(function(){
        var random = Math.random();
        if(typeof callback == 'function')
            callback(random);
        de.resolve(random);
    }, 1000);
    return de;
}

// 接受单个值
eval(Mr.Async.recode(function(){
    var random = $await(asyncRandom());
    console.log(random);
}));

// 多个值
eval(Mr.Async.recode(function(){
    var randoms = $await(asyncRandom(), asyncRandom());
    // 返回值为数组
    console.log(randoms[0]);
    console.log(randoms[1]);
}));

$await还可以用在循环中,现在支持的循环有for,while,do-while,看个经典例子,冒泡排序。

// 算法本身
function bubbleSort(arr){
    for(var i = 0 ; i < arr.length - 1 ; i++){
        for(var ii = i + 1; ii < arr.length ; ii++){
            if(arr[i] > arr[ii])
                normalSwap(arr, i, ii);
        }
    }
}

// 利用了Mr.Async.Interpreter的方式
eval(Mr.Async.recode(function(){
    for(var i = 0 ; i < arr.length - 1 ; i++){
        for(var ii = i + 1; ii < arr.length ; ii++){
            if(arr[i] > arr[ii])
                $await(asyncSwap(arr, i, ii));
        }
    }
}));

现在加强已经基本上满足需求了,支持的方式有

  • 赋值,var a = $await(delay()), b = 1, c = $await(delay());
  • 语句,$await(delay());
  • 循环中,for、while、do-while循环
  • if-else语句中

以上部分都进行过单元Testing,支持NodeJS,Testing用例详见interpreter-test.js,补充中。项目在GitHub,还是那句话,如果喜欢的话,多多关注。

TODO

目前还不支持的部分,有待补充:

  • return 语句,如 return $await(delay());
  • 并列赋值语句,如 var a, b, c; a = $await(delay()), b = 1, c = $await(delay());
  • 属性赋值,如a.result = $await(delay());
  • switch, try-catch-finally 语句,正在赶
  • Testing用例更新,bug fix
  • AOT预编译

题外话

老赵的《受禁锢的异步编程思维》对我影响很大,当然我不是他提到的异步编程思维被禁锢了的前端程序员,也相信很多从事前端的哥们儿也不是,这个我理解为是老赵的“偏见”了。真正影响的地方是他思考的不是JS或者C#方面的问题,他很辩证的看待各种技术的优劣势,了解背后的思想,并且加以推广,这种思想已经不只是集中到一个领域,而可以用到生活中的各个方面,这种想法是值得我们学习的。写到这,他为他的Jecex推广真是劳心劳力了,我在这也帮他推广下。我现在还“不敢”去看Jecex的源码,等到testcase更新完成之后,我去深入看看它,了解自己的差距在什么地方。

Comments

Proudly published with Hexo