-
Notifications
You must be signed in to change notification settings - Fork 1
Asynchronous Expression
当一个流程需要使用尾部异步调用时, 可以使用异步调用语法来减少代码缩进以及括号匹配的复杂度.
当形如
%
%()
%形参
%(形参列表)
的语素以函数调用的实参出现时, 称之为异步占位符. 其中形参为标识符; 形参列表为若干个标识符两两之间用逗号隔开.
当形如
%%
的语素以函数调用的实参出现时, 称之为正规异步占位符. 非正规异步占位符只能作为实参出现; 正规异步占位符可以作为实参出现, 还能作为函数定义或匿名函数的形参出现 (见正规异步函数定义).
当一个实际参数为异步占位符时, 该占位符表示一个匿名函数. 对于非正规异步占位符, 匿名函数的参数为形参列表中给出的参数, 或单个的形参, 或没有任何参数; 对于正规异步占位符, 其参数为 (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
.