二手货—加强Mr.Async
之前发过一条微博,“看得越多,想得越多,感觉差距就越大。如果我来写Jscex将会是啥样?”。这次的Mr.Async加强的就是类似于Jscex中$await方式开发异步操作。本来这篇文章的名字叫《二手货》,主要说明了这次加强完全是copy老赵的思想,在没真正理解他的想法之前,我曾经以为我的Mr.Async已经足够强大了,囧。我们平时接触的东西都是“二手的”,这种说法即便有些看轻自己,但我暂时用这说法来促使自己多去思考东西而不是用“明星都是从模仿开始的”这种来安慰自己。
这次的加强主要包含如下几个部分。
Mr.Async.Interpreter
Mr.Async.Interpreter.js 主要用于根据已有代码生成异步代码。其中包括这么两个部分:
- ExpressionVisitor.js 表达式访问器,类似C#中ExpressionVisitor类,表达式的生成由UglifyJS中的解释器来完成。此类可以单独使用,通过它的extend方法创建出扩展出的子类对象。下文的Mr.Recoder.js就是一个子类对象,使用方式详见它的代码。
- Mr.Recoder.js ExpressionVisitor的一个子类对象,它是包含了核心生成算法,其实算法说容易也容易,就是把表达式分析之后生成Mr.Async库中Mr.asynIterator和Mr.when方式的代码。说难就难在各种语句调用方式的处理,this作用域问题等等。
- parser.js UgilifyJS中JavaScript解析器,生成表达式树,库中的版本可以直接引用。如果需要引用更高版本的话,需要做点点改动,详细见:改动。
如果需要使用Mr.Async.Interpreter的话需要引入四个部分,如Testing用例文件,在NodeJS中只用引用Mr.Async.Interpreter.js就可以
- Recoder/ExpressionVisitor.js
- Recoder/parser.js
- Recoder/Mr.Recoder.js
- 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