Skip to content

Asynchronous Expression

Neuron Teckid edited this page Oct 22, 2015 · 1 revision

异步表达式

应用场景

当一个流程需要使用尾部异步调用时, 可以使用异步调用语法来减少代码缩进以及括号匹配的复杂度.

异步语法

异步占位符与异步调用

当形如

%
%()
%形参
%(形参列表)

的语素以函数调用的实参出现时, 称之为异步占位符. 其中形参为标识符; 形参列表为若干个标识符两两之间用逗号隔开.

当形如

%%

的语素以函数调用的实参出现时, 称之为正规异步占位符. 非正规异步占位符只能作为实参出现; 正规异步占位符可以作为实参出现, 还能作为函数定义或匿名函数的形参出现 (见正规异步函数定义).

当一个实际参数为异步占位符时, 该占位符表示一个匿名函数. 对于非正规异步占位符, 匿名函数的参数为形参列表中给出的参数, 或单个的形参, 或没有任何参数; 对于正规异步占位符, 其参数为 (error, result); 语义上, error 参数表示错误, result 表示结果.

当函数调用中某个参数为异步占位符时, 此调用称之为异步调用. 一个异步调用中只能出现一个异步占位符.

非正规异步调用本身的返回值仍然是该函数调用所返回的内容. 如

handle: setInterval(%, 2000)
console.log('after 2 seconds')
clearTimeout(handle)

正规异步调用的语法形式上的返回值为异步回调的结果. 如

content: fs.read('file', %%)
console.log(content.toString())

表达式 fs.read('file', %%) 的值会等同于 fs.read('file', function(error, result) { /* ... */ }) 中回调函数的 result 形参值.

异步调用对语句块编译过程的影响

异步调用出现后

  • 该异步调用所在的语句, 同一语句块其后的所有语句都将归入异步占位符所表示的函数体中
  • 异步调用本身与其返回值将在原有代码块中构成一个变量定义语句, 定义的变量名是全局唯一的, 初始值为该函数调用
  • 原语句用上述定义语句所定义的变量替换异步调用所在处

handle: setInterval(%, 2000)
console.log('after 2 seconds')
clearTimeout(handle)

异步调用 setInterval(%, 2000) 所在的语句 (定义 handle 的语句) 以及它之后同一语句块中的所有语句都将归入 setInterval 第一参数所表示的匿名函数的函数体中, 等价于产生如下 Javascript 代码

var handle;
var $ar = setInterval(function() {
    handle = $ar;
    console.log('after 2 seconds');
    clearTimeout(handle);
}, 2000);

正规异步调用

  • 该异步调用所在的语句, 同一语句块其后的所有语句都将归入异步占位符所表示的函数体中
  • 异步占位符所表示的回调函数体的第一个子句是分支语句, 条件为回调的 error 形式参数值, 条件若成立则执行错误回调或抛出该错误 (参见方法挑选)
  • 异步调用将在原有代码块中构成一个算术语句
  • 原语句用正规异步占位符所表示的回调的 result 形式参数的变量替换异步调用所在处, 且参数名应是全局唯一的

content: fs.read('file', %%)
console.log(content.toString())

异步调用 fs.read('file', %%) 所在的语句 (定义 content 的语句) 以及它之后同一语句块中的所有语句都将归入 fs.read 第二参数所表示的匿名函数的函数体中, 等价于产生如下 Javascript 代码

var content;
fs.read('file', function($error, $result) {
    if ($error)
        throw $error;
    content = $result;
    console.log(content);
});

求值顺序

如果一个语句中有多个异步调用, 则按照自左向右, 自参数向调用求值的顺序, 依次进行上述变换, 如

x: f(%%, g(%)) + h(%)
console.log(x)

得到的目标代码等价于

var x;
var $ar0 = g(function() {
    f(function($error, $result) {
        if ($error)
            throw $error;
        var $ar1 = h(function() {
            x = $result + $ar1;
            console.log(x);
        });
    }, $ar0);
});

异步管道

局限性

因为异步管道生成的代码不使用 for-in 循环, 因此无法获得 $k 代表的对象迭代的属性名. 因此, 建议仅在列表上进行异步管道迭代.

异步调用作为行间管道的后节

当一个管道表达式于管道运算符右侧的表达式中包含异步调用时, 该管道被标记为一个异步管道.

异步管道出现后对语句块的影响与异步调用类似, 但会先行对管道运算符左侧的列表 / 对象进行求值, 然后如下变换

  • 产生一个 next 函数, 该函数中有一个分支, 当管道运算符左侧列表迭代完毕时执行的分支称为后继分支, 另一分支称为迭代分支
  • 该异步管道所在的语句, 同一语句块其后的所有语句都将归入后继分支中
  • 迭代分支包含一个定义语句, 定义的变量名是全局唯一的, 初始值为该管道运算符右侧的表达式的值
  • 对管道运算符右侧的表达式进行异步调用变换, 但异步占位符所表示的匿名函数的函数体包括以下两个语句
  • 将上述定义语句中定义的变量加入结果列表; 继续迭代

如以下代码

['idA', 'idB', 'idC'] |: jQuery('#' + $).fadeTo(1000, 0.0, %)
console.log('After fading')

生成的代码等价于

var $list = ['idA', 'idB', 'idC'];
(function($list) {
    function next($index, $result) {
        if ($index === $list.length) {
            $result;
            console.log('After fading');
        } else {
            (function($element, $index) {
                var $key = null;
                var $ar = jQuery('#' + $element).fadeTo(1000, 0.0, function() {
                    $result.push($ar);
                    next($index + 1, $result);
                });
            })($list[$index], $index);
        }
    }
    next(0, []);
})($list);

在多行管道中使用异步调用

如果多行管道的末节中任何一个语句中包含有异步调用, 则编译时, 会按照类似上面行间管道的方式生成代码; 并且, 节中出现的每一条 return 语句, 都将被视作向结果列表中加入返回值, 并调用 next.

Clone this wiki locally