Подтвердить что ты не робот

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

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

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

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

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

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

Итак, у меня есть два вопроса.

  • Что это за шаблон?
  • Как это работает? Это может быть загруженный вопрос, но мне нравится знать, как что-то работает, когда кто-то спрашивает меня об этом, я могу дать им подробное объяснение.

Вот функция 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;
  };
};
4b9b3361

Ответ 1

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

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

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

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

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

Итак, чтобы обобщить, мы создаем цепочку функций, передавая наш код любой возвращаемой функции delayedAdd(2, 3). Эти функции возвратят другие функции, которые мы снова можем передать нашим функциям.

Надеюсь, что это поможет понять, что это работает несколько ясно, если вы не хотите больше спрашивать.

Ответ 2

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

Ответ 3

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

Caveat

Я не знаю, является ли выделенное codeis тем, что делает node.js(но это может быть...; -))

Ответ 4

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

Chaining

Здесь нет ничего особенного, потому что мы можем просто вернуть функцию, которая будет вызвана снова. Функции в 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 после математика Haskell Curry. Это определение

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

В основном, он принимает несколько аргументов и сливается с субсекунтами и применяет их к исходной функции, переданной в первом аргументе.

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

{Редактировать}

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

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) может быть реализовано как функция curring, в результате чего шаблон вашего примера дает вам этот

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

Резюме

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

Ответ 5

Все превосходные ответы здесь, особенно @mhlz и @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/

Ответ 6

Если мы будем расширять этот синтаксис логически, мы бы достигли чего-то вроде этого:

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