了解延迟执行链的语法
Understanding the syntax of a deferred execution chain
我正在学习JavaScript -真正的学习JavaScript。我有PHP背景,所以一些JavaScript概念对我来说还是很新的,尤其是异步编程。这个问题可能已经被回答过很多次了,但我一直没有找到一个答案。这可能是因为除了举个例子,我真的不知道怎么问这个问题。这里是:
当使用npm的延迟包时,我看到了下面的例子:delayedAdd(2, 3)(function (result) {
return result * result
})(function (result) {
console.log(result); // 25
});
他们把这称为链接,它实际上是工作的,因为我目前使用这段代码来检查承诺何时被解决或被拒绝。尽管他们称之为链接,但它让我想起了Swift中尾随的闭包。
我真的不明白这是什么类型的链接,因为我们有一个函数调用,然后紧接着,一个匿名函数被括号括起来。
我想我有两个问题。
- 这是什么模式?
- 它是如何工作的?这可能是一个含蓄的问题,但我喜欢知道一些东西是如何工作的,所以当有人问我这个问题时,我可以给他们一个详细的解释。
var delayedAdd = delay(function (a, b) {
return a + b;
}, 100);
使用以下函数:
var delay = function (fn, timeout) {
return function () {
var def = deferred(), self = this, args = arguments;
setTimeout(function () {
var value;
try {
value = fn.apply(self, args));
} catch (e) {
def.reject(e);
return;
}
def.resolve(value);
}, timeout);
return def.promise;
};
};
这其实很容易理解。让我们看看当表达式被求值时这里发生了什么:
首先调用delayedAdd(2, 3)
函数。它做一些事情,然后返回。"魔力"在于它的返回值是一个function
。更准确地说,它是一个至少需要一个参数的函数(我将回到这个问题)。
现在我们将delayedAdd(2, 3)
求值为一个函数,我们进入代码的下一部分,即左括号。开括号和闭括号当然是函数调用。因此,我们将调用delayedAdd(2, 3)
刚刚返回的函数,并向它传递一个参数,这是接下来定义的内容:
该参数是另一个函数(如您在示例中看到的)。此函数也接受一个参数(计算结果),并将其乘以自身返回。
第一次调用delayedAdd(2, 3)
返回的函数返回另一个函数,我们将再次调用该函数,参数为另一个函数(链的下一部分)。
delayedAdd(2, 3)
返回的函数来构建一个函数链。这些函数将返回其他函数,我们可以将这些函数再次传递给函数。
我希望这使它的工作方式有点清楚,如果不觉得自由地问更多。
mhz的回答非常清楚。作为补充,我在这里编写了一个delayedAdd
,以便您更好地理解这个过程
function delayedAdd(a, b) {
var sum = a + b
return function(f1) {
var result1 = f1(sum)
return function(f2) {
f2(result1)
}
}
}
在示例代码中,作为f1
传递的函数是:
function (result) {
return result * result
}
和f2
是:
function (result) {
console.log(result)
}
函数在JS中是一等公民——这意味着(除其他外),它们可以充当实际参数和函数返回值的角色。你的代码片段将函数映射到函数。
链式调用中函数的签名可能是这样的:
delayedAdd: number -> fn // returns function type a
a: fn ( number -> number) -> fn // returns function type b
b: fn ( number -> void ) -> void // returns nothing ( guessing, cannot know from your code portion )
一般设置
当然,JS是一种弱类型语言,所以列出的签名是通过猜测从代码片段中派生出来的。除了检查源代码之外,没有办法知道代码是否确实执行了上述建议。考虑到这出现在'链'的上下文中,签名可能看起来像这样:
delayedAdd: number x number -> fn (( fn T -> void ) -> ( fn T -> void ))
表示delayedAdd
将两个数映射到一个函数x
上,该函数将任意签名的函数映射到与自身相同签名的函数上。
那么谁会做这种事呢?为什么?
想象以下x
的实现:
//
// x
// Collects functions of unspecified (possibly implicit) signatures for later execution.
// Illustrative purpose only, do not use in production code.
//
// Assumes
function x ( fn ) {
var fn_current;
if (this.deferred === undefined) {
this.deferred = [];
}
if (fn === undefined) {
// apply functions
while ( this.deferred.length > 0 ) {
fn_current = this.deferred.shift();
this.accumulator = fn_current(this.accumulator);
}
return this.accumulator;
}
else {
this.deferred.push ( fn );
}
return this;
}
与一个函数delayedAdd
一起,该函数实际返回以下类型的对象…:
function delayedAdd ( a1, a2) {
return x ( function () { a1 + a2; } );
}
…您将有效地注册一个函数链,以便在稍后的某个时间点执行(例如在某个事件的回调中)。
笔记和提醒
- JS函数是JS对象 注册函数的签名实际上可以是任意的。考虑到它们是统一的,只是为了让这个说明更简单(嗯…)。
警告
我不知道是否概述的代码是什么node.js做(但它可能是…; -))
公平地说,此模式既可以是链接的,也可以是套用的(或部分应用程序)。取决于如何实现。注意,这是一个理论上的答案,它提供了关于模式的更多信息,而不是您的特定用例。
链接
这里没有什么特别的,因为我们可以返回一个将被再次调用的函数。javascript中的函数是一等公民
function delayedAdd(x, y) {
// In here work with x and y
return function(fn) {
// In here work with x, y and fn
return function(fn2) {
//Continue returning functions so long as you want the chain to work
}
}
}
在我看来,这使得它无法阅读。有更好的选择。
function delayedAdd(x, y) {
// In here work with x and y
return {
then: function(fn) {
// In here work with x, y and fn
return {
then: function(fn2) {
//Continue returning functions so long as you want the chain to work
}
}
}
}
}
这改变了从
调用函数的方式delayedAdd(..)(..)(..); // 25
转换为
delayedAdd().then().then()
不仅在传递多个回调函数时更具可读性,而且还允许与下一种称为柯里化的模式区分开来。
这个术语是以数学家Haskell Curry命名的。定义如下
在数学和计算机科学中,柯里化(currying)是一种将带有多个参数(或参数元组)的函数的求值转换为带有单个参数的函数序列的求值的技术(部分应用)。它是由Moses Schönfinkel引入的,后来由Haskell Curry开发。
它所做的基本上是接受几个参数,并与后续参数合并,并将它们应用于第一个参数传递的原始函数。
这是该函数的通用实现,取自Stefanv的Javascript Patterns。
{编辑}
我把以前的函数版本改成了一个包含了部分应用程序的版本,以使它成为一个更好的例子。在这个版本中,你必须调用没有参数的函数来获得返回的值,否则你将得到另一个部分应用的函数作为结果。这是一个非常基本的例子,更完整的例子可以在这篇文章中找到。
function schonfinkelize(fn) {
var slice = Array.prototype.slice,
stored_args = [],
partial = function () {
if (arguments.length === 0){
return fn.apply(null, stored_args);
} else {
stored_args = stored_args.concat(slice.call(arguments));
return partial;
}
};
return partial;
}
这是应用这个函数的结果
function add(a, b, c, d, e) {
return a + b + c + d + e;
}
schonfinkelize(add)(1, 2, 3)(5, 5)(); ==> 16
注意,add(或者在您的例子中是delayedAdd)可以作为居间函数实现,从而产生示例中的模式
delayedAdd(..)(..)(..); // 16
你不能仅仅通过查看函数调用的方式来得出关于模式的结论。仅仅因为你可以一个接一个地调用它并不意味着它是连锁的。这可能是另一种模式。这取决于函数的实现。
所有优秀的回答,特别是@mhlz和@Leo,我想谈谈你提到的链接部分。Leo的例子展示了调用像foo()()()
这样的函数的想法,但只适用于固定数量的回调。下面是一个实现无限链接的尝试:
delayedAdd = function da(a, b){
// a function was passed: call it on the result
if( typeof a == "function" ){
this.result = a( this.result )
}
else {
// the initial call with two numbers, no additional checks for clarity.
this.result = a + b;
}
// return this very function
return da;
};
现在你可以在第一次调用之后链接()
中的任意数量的函数:
// define some functions:
var square = function( number ){ return number * number; }
var add10 = function( number ){ return number + 10; }
var times2 = function( number ){ return number * 2; }
var whatIs = function( number ){ console.log( number ); return number; }
// chain them all!
delayedAdd(2, 3)(square)(whatIs)(add10)(whatIs)(times2)(whatIs);
// logs 23, 35 and 70 in the console.
http://jsfiddle.net/rm9nkjt8/3/
如果我们在逻辑上展开这个语法,我们会得到这样的东西:
var func1 = delayedAdd(2, 3);
var func2 = function (result) {
return result * result
};
var func3 = function (result) {
console.log(result);
};
var res = func1(func2); // variable 'res' is of type 'function'
res(func3);
- 无法在通过jQuery的ajax加载的页面中执行javascript
- JavaScript执行暂时挂起页面
- 如何在ReactJS JSX中执行嵌套的if-else语句
- 当js函数's已执行
- javascript自执行函数-不同的语法
- 未捕获的语法错误:无法在“文档”上执行“查询选择器”
- 将一个函数及其参数传递给另一个函数以执行它的语法是什么
- 是否有更好的语法来执行此代码
- 代码只是没有执行,可能有语法问题,或者我可能完全错了
- 未捕获的语法错误尝试执行反向地理编码
- 解释了自执行函数语法和回调语法
- “执行此操作或抛出错误”的简洁语法
- Javascript自执行语法
- 在执行razor语法后执行脚本
- 满足条件时执行异步函数的方便语法
- javascript在打印新标签时执行神秘函数语法
- 立即/自执行函数语法:括号与否定运算符(和其他运算符)
- 自执行匿名函数语法
- 当尝试执行JavaScript文件时,给了我一个:语法错误:意外的令牌)
- 了解延迟执行链的语法