Comprendre la syntaxe d'une chaîne d'exécution différée

je me déplace pour apprendre le JavaScript - vraiment apprendre le JavaScript. Je viens d'un fond PHP donc certains concepts JavaScript sont encore Nouveaux pour moi, en particulier la programmation asynchrone. Cette question a peut-être déjà reçu une réponse plusieurs fois auparavant, mais je n'ai pas pu trouver de réponse. C'est peut-être parce que je ne sais même pas comment poser la question autrement qu'en montrant un exemple. Alors voilà:

Lorsqu'on utilise le différé paquet de npm, je vois l'exemple suivant:

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

ils se réfèrent à cela comme enchaînement et cela fonctionne réellement comme je suis en train d'utiliser ce code pour vérifier quand une promesse est résolue ou est rejetée. Même s'ils l'appellent enchaînement, cela me rappelle les fermetures à la traîne comme dans Swift.

Je ne comprends pas vraiment de quel type de chaînage il s'agit puisque nous avons une fonction invocation et puis immédiatement après, une fonction anonyme entre parenthèses.

donc je suppose que j'ai deux questions.

  1. Quel Est ce schéma?
  2. comment ça marche? C'est peut-être une question chargée, mais j'aime savoir comment quelque chose fonctionne, donc quand quelqu'un me pose des questions à ce sujet, je peux lui donner une explication détaillée.

Voici la fonction delayedAdd:

var delayedAdd = delay(function (a, b) {
  return a + b;
}, 100);

qui utilise la fonction suivante:

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;
  };
};
28
demandé sur 200_success 2015-06-02 16:04:59

6 réponses

C'est en fait très facile à comprendre. Regardons ce qui se passe ici lorsque l'expression est évaluée:

D'abord la fonction delayedAdd(2, 3) sera appelée. Il fait des trucs et il revient. La "magie" est tout au sujet de sa valeur de retour, qui est un function . Pour être plus précis c'est une fonction qui attend au moins un argument (je vais y revenir).

Maintenant que nous avons évalué delayedAdd(2, 3) à une fonction, nous arrivons à la prochaine une partie du code, qui est la parenthèse d'ouverture. Les parenthèses d'ouverture et de fermeture sont bien sûr des appels de fonction. Nous allons donc appeler la fonction que delayedAdd(2, 3) vient de retourner et nous allons lui passer un argument, ce qui est ce qui est défini ensuite:

cet argument est encore une autre fonction (comme vous pouvez le voir dans l'exemple). Cette fonction prend aussi un argument (le résultat du calcul) et le renvoie multiplié par lui-même.

This la fonction qui a été retournée par le premier appel à delayedAdd(2, 3) renvoie encore une autre fonction, que nous appellerons à nouveau avec un argument qui est une autre fonction (la partie suivante de la chaîne).

donc pour résumer nous construisons une chaîne de fonctions en passant notre code à n'importe quelle fonction delayedAdd(2, 3) retourné. Ces fonctions retourneront d'autres fonctions que nous pouvons passer nos fonctions à nouveau.

j'espère que cela fait la façon dont il fonctionne assez clair, sinon n'hésitez pas à demander plus.

22
répondu mhlz 2015-06-02 13:19:48

la réponse de mhlz est très claire. En guise de complément, je compose ici un delayedAdd pour que vous puissiez mieux comprendre le processus

function delayedAdd(a, b) {
  var sum = a + b
  return function(f1) {
    var result1 = f1(sum)
    return function(f2) {
      f2(result1)
    }
  }
}

où dans votre exemple de code, la fonction que vous avez passée comme f1 est:

function (result) {
  return result * result
}

et f2 est:

function (result) {
  console.log(result)
}
14
répondu Leo 2015-06-02 13:23:41

fonctions sont des citoyens de première classe dans JS - cela signifie (entre autres), ils peuvent prendre le rôle de paramètres réels et les valeurs de retour de fonction. Votre fragment de code établit une correspondance entre les fonctions et les fonctions.

les signatures des fonctions de votre appel enchaîné pourraient ressembler à ceci.

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 )

réglage Général

bien sûr, JS est un langage faiblement typé, donc les signatures listées sont dérivées du fragment de code par supposition. Il n'y a aucun moyen de savoir si le code fait réellement ce qui est suggéré ci-dessus en dehors de l'inspection des sources.

étant donné que ceci est apparu dans le contexte du "chaînage", les signatures ressemblent probablement plutôt à ceci:

delayedAdd: number x number -> fn (( fn T -> void ) -> ( fn T -> void ))

, ce qui signifie que delayedAdd établit une correspondance entre deux nombres et une fonction x , qui établit une correspondance entre les fonctions de signatures arbitraires et les fonctions de la même signature qu'elle-même.

alors qui ferait une chose pareille ? Et pourquoi ?

Imaginez la mise en œuvre suivante de 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;
 }

avec une fonction delayedAdd qui renvoie en fait un objet du genre suivant ...:

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

... vous enregistrerez effectivement une chaîne de fonctions à exécuter à un moment ultérieur (par exemple lors d'un rappel à un événement).

Notes et rappels

  • les fonctions de JS sont des objets js
  • les signatures des fonctions enregistrées peuvent en fait être arbitraires. Considérer unifiée sert simplement à maintenir cette exposition plus simple (bien ...).

mise en garde

Je ne sais pas si le code décrit est quel noeud.js si (mais ça pourrait l'être ... ;-))

7
répondu collapsar 2015-06-02 14:01:02

pour être juste, ce modèle peut être soit chaînage ou currying(ou application partielle). Cela dépend de la façon dont il est mis en œuvre. Remarque Il s'agit d'une réponse théorique pour fournir plus d'information sur le modèle et non sur votre cas d'utilisation spécifique.

chaînage

il n'y a rien de spécial ici parce que nous pouvons simplement retourner une fonction qui sera appelée à nouveau. Les fonctions en javascript sont des citoyens de première classe

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

cela le rend illisible à mon avis. Il y a une meilleure alternative.

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

cela change la façon dont vos fonctions sont appelées de

delayedAdd(..)(..)(..); // 25 

est transformé en

delayedAdd().then().then()

N'est pas seulement plus lisible quand vous passez plusieurs fonctions de rappel mais permet une distinction du prochain modèle appelé currying.

Nourrissage

le terme cames d'après le mathématicien Haskell Curry . La définition est cette

en mathématiques et en informatique, le currying est la technique de traduction de l'évaluation d'une fonction qui prend plusieurs arguments (ou un tuple d'arguments) dans l'évaluation d'une séquence de fonctions, chacune avec un seul argument (application partielle). Il a été introduit par Moses Schönfinkel et développé plus tard par Haskell Curry.

fondamentalement, ce qu'il fait est de prendre plusieurs arguments et de fusionner avec les sous-composants et de les appliquer à la fonction originale passée dans le premier argument.

il s'agit d'une implémentation générique de cette fonction tirée des motifs Javascript de Stefanv.

{Edit}

j'ai changé ma version précédente de la fonction d'une application partielle inclus pour faire un meilleur exemple. Dans cette version, vous devez appeler la fonction sans argument pour obtenir la valeur retournée ou vous obtiendrez une autre fonction partiellement appliquée comme résultat. C'est un exemple très basique, un plus complet peut être trouvé sur ce post .

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

Ce sont les résultats de l'application de cette fonction

 function add(a, b, c, d, e) {
     return a + b + c + d + e;
 }
 schonfinkelize(add)(1, 2, 3)(5, 5)(); ==> 16

notez que add (ou dans votre cas delayedAdd) peut être implémenté comme le fonction curying résultant dans le modèle de votre exemple vous donnant ce

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

résumé

vous ne pouvez pas arriver à une conclusion sur le modèle simplement en regardant la façon dont les fonctions sont appelées. Ce n'est pas parce qu'on peut invoquer l'un après l'autre que ça veut dire qu'on s'enchaîne. Ça pourrait être un autre schéma. Qui dépend de la mise en œuvre de la fonction.

6
répondu devconcept 2017-05-23 10:29:01

toutes les excellentes réponses ici, en particulier @mhlz et @Leo, je voudrais aborder la partie chaînage vous avez mentionné. L'exemple de Leo montre l'idée d'appeler des fonctions comme foo()()() mais ne fonctionne que pour un nombre fixe de callbacks. Voici une tentative de chaînage illimité 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;
};

Maintenant vous pouvez enchaîner n'importe quel nombre de fonctions dans () après le premier appel:

// 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 /

5
répondu pawel 2015-06-02 13:47:16

si nous développons cette syntaxe logiquement, nous atteindrons quelque chose comme ceci:

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);
2
répondu Hi I'm Frogatto 2015-06-02 13:30:24