Понимание синтаксиса цепочки отложенного выполнения

Я собираюсь изучать JavaScript-действительно изучать JavaScript. Я пришел из PHP-фона, поэтому некоторые концепции JavaScript все еще новы для меня, особенно асинхронное программирование. На этот вопрос можно было бы ответить уже много раз, но я не смог найти ответа. Это может быть потому, что я даже не знаю, как задать вопрос, кроме как показывая пример. Вот оно:

при использовании отложила пакет из npm я вижу следующий пример:

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

6 ответов


это на самом деле очень легко понять. Давайте посмотрим, что происходит здесь, когда выражение оценивается:

сначала delayedAdd(2, 3) функция будет вызвана. Он что-то делает, а потом возвращается. "Магия" - это все о его возвращаемом значении, которое является function. Точнее, это функция, которая ожидает хотя бы один аргумент (я вернусь к этому).

теперь, когда мы оценили delayedAdd(2, 3) к функции мы переходим к следующей части кода, которая является открытием скобка. Открытие и закрытие скобки, конечно, вызовы функций. Поэтому мы будем называть функцию that delayedAdd(2, 3) только что вернулся, и мы собираемся передать ему аргумент, который определяется следующим образом:

этот аргумент является еще одной функцией (как вы можете видеть в своем примере). Эта функция принимает один аргумент (результат вычисления) и возвращает его умножить на себя.

эта функция, которая была возвращена при первом вызове delayedAdd(2, 3) возвращает еще еще одна функция, которую мы снова вызовем с аргументом другое


ответ 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)
}

функции являются первоклассными гражданами в 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
  • подписи зарегистрированных функций могут на самом деле будьте произвольны. Рассмотрение их как единых просто служит для того, чтобы упростить эту экспозицию (ну ...).

предостережение

Я не знаю, является ли описанный код каким узлом.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()

не только более читаем, Когда вы передаете несколько функций обратного вызова, но и позволяет отличить от следующего шаблона, называемого currying.

карринг

термин cames после математика Хаскелл Карри. Определение это

в математике и информатике карринг-это метод перевода оценки функции, которая принимает несколько аргументов (или кортеж аргументов) в оценку последовательности функций, каждая с одним аргументом (частичное применение). Он был введен Моисеем Schönfinkel и позже разработанная Хаскелла Карри.

это займет несколько аргументов и слиться с subsecuents и применить их к исходная функция передается в первом аргументе.

это общая реализация этой функции, взятая из шаблонов Javascript Stefanv.

{Edit}

Я изменил свою предыдущую версию функции на ту, в которую включено частичное приложение, чтобы сделать лучший пример. В этой версии вы должны вызвать функцию без аргумента, чтобы получить возвращаемое значение, или вы получите другую частично примененную функцию в результате. Это очень основной пример, более полный можно найти на этот пост.

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

резюме

вы не можете прийти к выводу о узор, просто глядя на то, как функции называемый. Только потому, что вы можете вызывать один за другим, это не означает, что это цепочка. Это может быть другая схема. Это зависит от выполнения функции.


все отличные ответы здесь, особенно @mhlz и @Leo, я хотел бы коснуться сцепление часть, которую вы уже упоминали. Пример Лео показывает идею вызова таких функций, как foo()()() но работает только для фиксированного количества повторных вызовов. Вот попытка imlpement неограниченное сцепление:

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