理解延迟执行链的语法

Understanding the syntax of a deferred execution chain

我正忙着学习 JavaScript - 真的在学习 JavaScript。我来自 PHP 背景,所以一些 JavaScript 概念对我来说仍然是新的,尤其是异步编程。这个问题以前可能已经回答过很多次了,但我一直找不到答案。这可能是因为除了举个例子,我什至不知道如何提出问题。所以这里是:

使用 npm 中的 deferred package 时,我看到以下示例:

delayedAdd(2, 3)(function (result) {
  return result * result
})(function (result) {
  console.log(result); // 25 
});

他们将此称为链接,它确实有效,因为我目前正在使用此代码来检查承诺何时被解决或被拒绝。尽管他们称之为链接,但它让我想起了 Swift.

中的尾随闭包

我真的不明白这是什么类型的链接,因为我们有一个函数调用,紧接着是一个用括号括起来的匿名函数。

所以我想我有两个问题。

  1. 这是什么图案?
  2. 它是如何工作的?这可能是一个很复杂的问题,但我想知道某些东西是如何工作的,所以当有人问我这个问题时,我可以给他们一个详细的解释。

这里是 delayedAdd 函数:

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) 函数。它做了一些事情然后 returns。 "magic" 就是它的 return 值,即 function。更准确地说,它是一个至少需要一个参数的函数(我会回到那个)。

现在我们已将 delayedAdd(2, 3) 计算为一个函数,我们将进入代码的下一部分,即左括号。左括号和右括号当然是函数调用。所以我们要调用 delayedAdd(2, 3) 刚刚 returned 的函数,我们将向它传递一个参数,这是接下来定义的:

该参数是另一个函数(如您在示例中所见)。此函数也接受一个参数(计算结果)并且 return 它自乘。

这个函数在第一次调用 delayedAdd(2, 3) 时被 return 编辑 return 是另一个函数,我们将使用参数 [=30= 再次调用它]另一个函数(链的下一部分)。

总而言之,我们通过将代码传递给 delayedAdd(2, 3) returned 的任何函数来构建函数链。这些函数将 return 其他函数,我们可以再次传递我们的函数。

我希望这能让它的工作方式变得清晰一些,如果不明白,请随时提出更多问题。

函数是 JS 中的第一个 class 公民 - 这意味着(除其他外),它们可以充当实际参数和函数 return 值的角色。您的代码片段将函数映射到函数。

链接调用中函数的签名可能如下所示。

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是一门弱类型语言,所以列出的签名都是从代码片段中猜测出来的。除了检查源代码之外,没有办法知道代码是否真的按照上面的建议执行。

鉴于这出现在 'chaining' 的上下文中,签名可能看起来像这样:

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

连同实际上 return 是以下类型的对象的函数 delayedAdd ...:[=​​18=]

 function delayedAdd ( a1, a2) {
     return x ( function () { a1 + a2; } );
 }

...您将有效地注册要在稍后某个时间点执行的函数链(例如,在对某个事件的回调中)。

备注和提醒

  • JS 函数是 JS 对象
  • 注册函数的签名实际上可能是任意的。将它们统一考虑只是为了让这个阐述更简单(好吧......)。

警告

我不知道概述的代码是否是 node.js 所做的(但它可能是......;-))

mhlz的回答很明确。作为补充,这里我编一个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)
}

如果我们从逻辑上扩展这个语法,我们会得到这样的结果:

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

这里有所有出色的答案,尤其是@mhlz 和@Leo,我想谈谈您提到的 chaining 部分。 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/

公平地说,此模式可以是链接或柯里化(或部分应用)。取决于它的实施方式。请注意,这是提供有关模式的更多信息的理论答案,而不是您的特定用例。

链接

这里没有什么特别的,因为我们可以 return 一个将被再次调用的函数。 javascript 中的函数首先是 class 公民

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之后。定义是这样

In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument (partial application). It was introduced by Moses Schönfinkel and later developed by Haskell Curry.

基本上它所做的是接受几个参数并与子参数合并并将它们应用于第一个参数中传递的原始函数。

这是该函数的通用实现,取自 Stefanv 的 Javascript 模式。

{编辑}

我将该函数的先前版本更改为包含部分应用程序的版本,以提供更好的示例。在此版本中,您必须调用不带参数的函数来获取值 returned,否则您将得到另一个部分应用的函数作为结果。这是一个非常基本的示例,可以在 .

上找到更完整的示例
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)可以实现为 curying 函数,从而导致您的示例模式为您提供此

delayedAdd(..)(..)(..); // 16

总结

您无法仅通过查看函数的调用方式来得出有关模式的结论。仅仅因为您可以一个接一个地调用它并不意味着是链接。这可能是另一种模式。这取决于函数的实现。