Jscex与Promise/A那些事
发布于 3年前 作者 jeffz 3402 次浏览

原文链接:Jscex与Promise/A那些事

任何异步编程的类库要做的第一件事往往便是统一异步编程的模型,例如Jscex异步模块自带一个类似于.NET中的异步任务模型。围绕统一的模型,开发人员便可以尽情地提供各种扩展,例如Jscex异步增强模块中的whenAll或whenAny一样。换句话说,假如要混用两种异步编程模型,往往需要将其中一种适配至另外一种,因此异步增强模块中也提供了fromCallback及fromStandard辅助,能够轻易地将最简单的(也是Node.js里使用的)两种异步函数接口绑定为异步任务。那么Promise/A呢?它也是种目前运用十分广泛的异步编程模型,Jscex对它有什么特别的支持吗?当然有,但方式有所不同,更为直接。

Promise/A现在为CommonJS的草案之一,提出了一种Promise模型的设计及API表现。虽说它离“标准”还有很长一段距离,但其实很多类库都已经实现了这个规范了,例如著名的jQuery,node-promise,还有用来编写Win8中Metro应用的HTML5开发平台。当然严格来说,它们都是基于Promise/A规范的一套“扩展实现”,但既然有了共有的子集,那事情就已经好办多了。例如,之前在一个QQ群上某同学建议我提供一个类似于fromStandard一样的fromPromise辅助方法。这当然没问题,其实很简单,接下来也会做,但Jscex考虑地更多。

或者说,就是多问了几个为什么:

为什么需要fromPromise辅助方法?因为用户使用了Promise异步模型,而Jscex希望提供更好的辅助环境。为什么对方不使用Jscex自带的异步任务模型?因为用户可能已经有部分代码采用了Promise模型。为什么它要使用Promise这种已经较为成熟且复杂的异步模型?因为用户可能已经有了一个围绕着Promise模型开发的应用程序,甚至是一个已经拥有大量辅助方法支持的应用开发框架(例如Win8),而在这个情况下再结合Jscex的异步任务模型,则需要来回转换,显得略为冗余。那么,Jscex能否直接对Promise异步模型提供支持呢?

当然可以,从一开始Jscex就是这么设计的,且看这个示例

Jscex.Promise.create = function (fn) {
    var dfd = new $.Deferred();
    fn(dfd.resolve, dfd.reject);
    return dfd.promise();
}

var oneRoundTripAsync = eval(Jscex.compile(“promise”, function () { $await($(“#block”).animate({ left: “200px” }, 1000).promise()); $await($(“#block”).animate({ left: “0px” }, 1000).promise()); }));

var roundTripsAsync = eval(Jscex.compile(“promise”, function (n) { for (var i = 0; i < n; i++) { $await(oneRoundTripAsync()); } }));

这是用jQuery自带的animate方法创建动画的示例。正如我之前说的那样,各个模型其实都是基于Promise/A的“扩展”,因此Jscex无法提供一种另所有人都满意的Promise模型,于是它谁都不去迎合,将构造Promise对象的任务交给使用者——这便是上面代码中提供Jscex.Promise.create方法的原因。之后便可以像使用Jscex异步模型那样创建和使用异步方法了,区别仅仅是:

  • 使用promise作为构造器的名称。
  • 异步方法返回的都将是Promise对象。
  • $await操作接受的参数也是Promise对象。
  • 执行异步方法之后,异步操作已经直接启动了,而无需调用start方法。

而想在Win8里开发也一样,首先提供一个用于创建Promise对象的工厂方法:

Jscex.Promise.create = function (fn) {
    return new WinJS.Promise(fn);
}

然后便可以将以下这段使用回调实现的“提示”、“显示选择器”、“显示图片”这个事务:

var MessageDialog = Windows.UI.Popups.MessageDialog;
var UICommand = Windows.UI.Popups.UICommand;
var FileOpenPicker = Windows.Storage.Pickers.FileOpenPicker;
var PickerViewMode = Windows.Storage.Pickers.PickerViewMode;
var PickerLocationId = Windows.Storage.Pickers.PickerLocationId;
var FileAccessMode = Windows.Storage.FileAccessMode;

WinJS.Namespace.define(“MyApp”, { showPhoto: function () { var dlg = new MessageDialog(“Do you want to open a file?”); dlg.commands.push(new UICommand(“Yes”, null, “Yes”)); dlg.commands.push(new UICommand(“No”, null, “No”));

    dlg.showAsync().then(<span style="color: blue">function </span>(result) {
        <span style="color: blue">if </span>(result.id == <span style="color: maroon">&quot;Yes&quot;</span>) {
            <span style="color: blue">var </span>picker = <span style="color: blue">new </span>FileOpenPicker();
            picker.viewMode = PickerViewMode.thumbnail;
            picker.suggestedStartLocation = PickerLocationId.picturesLibrary;
            picker.fileTypeFilter.push(<span style="color: maroon">&quot;.jpg&quot;</span>);

            picker.pickSingleFileAsync().then(<span style="color: blue">function </span>(file) {
                <span style="color: blue">if </span>(file != <span style="color: blue">null</span>) {
                    $(<span style="color: maroon">&quot;#myImg&quot;</span>)[0].src = URL.createObjectURL(file);
                }
            });
        }
    });
}

});

实现为:

WinJS.Namespace.define("MyApp", {
    showPhoto: eval(Jscex.compile("promise", function () {
        var dlg = new MessageDialog("Do you want to open a file?");
        dlg.commands.push(new UICommand("Yes", null, "Yes"));
        dlg.commands.push(new UICommand("No", null, "No"));
    <span style="color: blue">var </span>result = $await(dlg.showAsync());
    <span style="color: blue">if </span>(result.id == <span style="color: maroon">&quot;Yes&quot;</span>) {
        <span style="color: blue">var </span>picker = <span style="color: blue">new </span>FileOpenPicker();
        picker.viewMode = PickerViewMode.thumbnail;
        picker.suggestedStartLocation = PickerLocationId.picturesLibra
        picker.fileTypeFilter.push(<span style="color: maroon">&quot;.jpg&quot;</span>);

        <span style="color: blue">var </span>file = $await(picker.pickSingleFileAsync());
        <span style="color: blue">if </span>(file != <span style="color: blue">null</span>) {
            $(<span style="color: maroon">&quot;#myImg&quot;</span>).src = URL.createObjectURL(file);
        }
    }
}))

});

Jscex这不又瞬间支持了Win8开发了吗?此时所有的Jscex异步函数都会返回一个Promise对象,它和WinJS中各种表达异步操作的Promise对象完全相同,也可以和Promise.join以及Promise.any共同使用。而且,实现一个支持Promise的Jscex构造器只需要30多行代码,其中相当部分还是函数定义等架子代码,创建一个Jscex构造器的大部分代码都已经由构造器基础模块提供了。换句话说,假如您的应用中已经有个深入骨髓的异步模型,也只需30多行代码,便可以直接在Jscex中使用了。

这也是Jscex的精妙之处之一:一个简单统一的结构,可以实现出各种灵活的功能。

17 回复

话说写完这篇文章连我都觉得Jscex太强大了……

感觉都是一门新的语言了, 除了语法是 JS. 玩 AST 就是比 JS 好玩 ╰( ̄▽ ̄)╭

~~ 很叼呀!看来得加紧时间学习老赵的Jscex~~

语法跟JS都一样还叫新语言?

@jeffz 真心羡慕能这么转换… Scheme 几门语言不都能取不同名字的… 而且语法不就几个体系的嘛. 我也只是出于个人喜好不开心花括号语法.

@jeffz Guile 和 Racket 都是 Scheme 的语法, 却当不同的语言, 光说语法 Lisp 大多方言之间差距都很小的, 我不赞成说语法不同意味着不同的语言. 很多新语言看语法一般能找到对应的源头, 经常还是函数式语言里来. 用花括号作为语法部件的语言因为太常用, 很有种相互模仿的感觉, 而且编程的重点本来也不是为了争语法… 我还以为楼上第一条回复在指别的什么… 至少我不熟语法解析的代码, 不能楼上那么深去理解了, 我要错了您说我吧… 然后因为一些原因我觉得这比 CoffeeScript 差别 JS 还大了

@jiyinyiyong 愈发听不懂了……还有你觉得Jscex相比JS的差别,比Coffee跟JS的差别还大?

@jeffz 天哪. 我没多少开发的经验前边的我理不出重点了. 我觉得 coffee 像 JS 因为内容基本一致, 记得有过 Iced 给 coffee 加添加异步的关键字现在也还没加上, 而且我先熟悉 coffee 然后才混熟 JS 环境的, 没感到需要什么转换思路的东西(也可能因为面向对象接触少). 而 Jscex, 或者是我代码经验太少所以没能很快适应. 我感觉是这样. 可以当我特例.

@jiyinyiyong 我觉得还是表达方面的问题,就比如“我没多少开发的经验前边的我理不出重点了”这句我又听不懂了……我觉得你真是特例,第一次有人感觉CoffeeScript更接近JS的…… 其实Jscex叫做“思路转换”我也感觉冤枉,原本就是让你用传统思路写代码。就好比,你已经学会走路了,但是在雪地里你必须趴着才能前进,后来给你个雪橇让你可以继续走,结果你说这改变了我前进方式,不太适应,在我看来这不是蛋疼么……

@jeffz 没技能没底气. 就这样子的. 我刚开始因为 C 和 JS 花括号太多写练习的代码都写不下去, 甚至努力翻其他语法风格的语言尝试去学, 反正就是走进歧途了.但后面看着 JS 的文档去写 coffee 的代码, 解决问题的思路不觉得有变化. 后面的雪橇, 我想是因为我没有遇到足够充分的场景, 而且我入门就在浏览器环境, 好多习俗我还没懂的. 不考虑是否疼.

@sumory 不过紧张死我了, 本来是来羡慕 AST 的, 一不小心敏感话题了.

@jeffz @jiyinyiyong 的意思应该是CoffeeScript虽然语法跟js不同,但也只是简单转换一下而已,还是用js的那套思路。而要用Jscex,首先得写一堆所谓的“其中相当部分还是函数定义等架子代码”,粗略一看,觉得很难

@leizongmin 你摘的这句话完全不是讲Jscex怎么用的,而是说Jscex怎么做出来的……难道加一个eval(Jscex.compile())就是那么大的阻碍?

@jeffz 这个障碍还真是挺大的,如果在任意地方直接使用$await就爽很多了

@leizongmin 这个是用来区分Jscex函数和普通函数分界,两种函数有截然不同的含义。还有实在理解不了这个障碍能有多大,完全没有变化的架子代码。

回到顶部