Jscex与Promise/A那些事
发布于 12 年前 作者 jeffz 7785 次浏览 最后一次编辑是 8 年前

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

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

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

<p>或者说,就是多问了几个为什么:</p>

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

<p>当然可以,从一开始Jscex就是这么设计的,且看<a href=“http://repository.jscex.info/master/samples/promise/jquery-animation.html” data-popup-height=“200” data-popup-width=“300” data-popup=“iframe”>这个示例</a>:</p>

<pre class=“code”>Jscex.Promise.create = <span style=“color: blue”>function </span>(fn) { <span style=“color: blue”>var </span>dfd = <span style=“color: blue”>new </span>$.Deferred(); fn(dfd.resolve, dfd.reject); <span style=“color: blue”>return </span>dfd.promise(); }

<span style=“color: blue”>var </span>oneRoundTripAsync = eval(Jscex.compile(<span style=“color: maroon”>“promise”</span>, <span style=“color: blue”>function </span>() { $await($(<span style=“color: maroon”>"#block"</span>).animate({ left: <span style=“color: maroon”>“200px” </span>}, 1000).promise()); $await($(<span style=“color: maroon”>"#block"</span>).animate({ left: <span style=“color: maroon”>“0px” </span>}, 1000).promise()); }));

<span style=“color: blue”>var </span>roundTripsAsync = eval(Jscex.compile(<span style=“color: maroon”>“promise”</span>, <span style=“color: blue”>function </span>(n) { <span style=“color: blue”>for </span>(<span style=“color: blue”>var </span>i = 0; i < n; i++) { $await(oneRoundTripAsync()); } }));</pre>

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

<ul> <li>使用promise作为构造器的名称。 </li>

<li>异步方法返回的都将是Promise对象。 </li>

<li>$await操作接受的参数也是Promise对象。 </li>

<li>执行异步方法之后,异步操作已经直接启动了,而无需调用start方法。 </li> </ul>

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

<pre class=“code”>Jscex.Promise.create = <span style=“color: blue”>function </span>(fn) { <span style=“color: blue”>return new </span>WinJS.Promise(fn); }</pre>

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

<pre class=“code”><span style=“color: blue”>var </span>MessageDialog = Windows.UI.Popups.MessageDialog; <span style=“color: blue”>var </span>UICommand = Windows.UI.Popups.UICommand; <span style=“color: blue”>var </span>FileOpenPicker = Windows.Storage.Pickers.FileOpenPicker; <span style=“color: blue”>var </span>PickerViewMode = Windows.Storage.Pickers.PickerViewMode; <span style=“color: blue”>var </span>PickerLocationId = Windows.Storage.Pickers.PickerLocationId; <span style=“color: blue”>var </span>FileAccessMode = Windows.Storage.FileAccessMode;

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

    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);
                }
            });
        }
    });
}

});</pre>

<p>实现为:</p>

<pre class=“code”>WinJS.Namespace.define(<span style=“color: maroon”>“MyApp”</span>, { showPhoto: eval(Jscex.compile(<span style=“color: maroon”>“promise”</span>, <span style=“color: blue”>function </span>() { <span style=“color: blue”>var </span>dlg = <span style=“color: blue”>new </span>MessageDialog(<span style=“color: maroon”>“Do you want to open a file?”</span>); dlg.commands.push(<span style=“color: blue”>new </span>UICommand(<span style=“color: maroon”>“Yes”</span>, <span style=“color: blue”>null</span>, <span style=“color: maroon”>“Yes”</span>)); dlg.commands.push(<span style=“color: blue”>new </span>UICommand(<span style=“color: maroon”>“No”</span>, <span style=“color: blue”>null</span>, <span style=“color: maroon”>“No”</span>));

    <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);
        }
    }
}))

});</pre>

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

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

17 回复

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

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

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

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

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

@jiyinyiyong 听不懂……

@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函数和普通函数分界,两种函数有截然不同的含义。还有实在理解不了这个障碍能有多大,完全没有变化的架子代码。

回到顶部