2017-10-19 7 views
0

J'écris la fonction Node.JS lambda, où j'ai besoin d'appeler une fonction API avec callback et d'y passer des paramètres supplémentaires.Rappel avec des paramètres supplémentaires dans la boucle

Le code ressemble à:

var s3 = ...; 
for (var i = 0; i < data.foo.length; i++) { 
    var v1 = data.foo[i].name; 
    console.log("Loop: " + v1); 
    var params = { ... };    
    foo.method1(params, function(err, data1) { 
     console.log("Method1: " + v1); 
     s3.putObject(....); 
    }); 
} 

Voici deux problèmes.

  1. Je ne comprends pas pourquoi, mais à l'intérieur du rappel passé à foo.method1... j'ai toujours la même valeur de v1 (je suppose qu'il est le dernier à partir du tableau).

  2. Le contrôleur de code de la console Amazon me conseille qu'il est une mauvaise pratique pour créer une fonction dans une boucle:

Ne faites pas de fonctions dans une boucle.

Je suppose que p.1 est en quelque sorte lié à p.2 :-) Voilà pourquoi j'ai essayé de créer une fonction nommée et de transmettre sa référence à foo.method1. Mais cela ne fonctionne pas car je ne peux pas passer les paramètres supplémentaires v1 et s3 là. Je ne peux terminer son appel comme:

foo.method1(params, function(err, data1) { 
      myCallback(err, data1, v1, s3); 
    }); 
  • ce qui n'a pas de sens, car le résultat est le même.

Conseil: foo.method1 est apparemment asynchrone.

Je doute de la façon de résoudre ce problème?

+0

'v1' est pris dans la fermeture de chaque fonction. Toutes les fonctions se réfèrent à la même copie en direct de 'v1', qui sera la dernière valeur au moment où les fonctions sont appelées. Une solution consiste à utiliser 'let', comme décrit ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Cleaner_code_in_inner_functions – skirtle

Répondre

2

Il s'agit d'un problème commun connu sous le nom de "fermeture sur une variable de boucle". Le problème est que v1 continuera à changer pendant que vous attendez que le rappel s'exécute et le v1 que vous voyez sera la dernière valeur qu'il prend.

Une façon de résoudre ce problème: lier la variable v1 dans votre rappel:

var s3 = ...; 
for (var i = 0; i < data.foo.length; i++) { 
    var v1 = data.foo[i].name; 
    console.log("Loop: " + v1); 
    var params = { ... };   // v-- extra parameter 
    foo.method1(params, function(v1inner, err, data1) { 
     console.log("Method1: " + v1); 
     s3.putObject(....); 
    }.bind(null, v1)); // <-- bind 
} 

Une meilleure approche: utiliser .forEach de sorte qu'une nouvelle variable v1 est créée pour chaque itération:

var s3 = ...; 
data.foo.forEach(function (datai) { 
    var v1 = datai.name; 
    console.log("Loop: " + v1); 
    var params = { ... };    
    foo.method1(params, function(err, data1) { 
     console.log("Method1: " + v1); 
     s3.putObject(....); 
    }); 
}); 

Editer: Si vous cherchez un moyen plus sûr de travailler avec du code asynchrone, je vous suggère de regarder dans Promises, qui, à partir de maintenant, sont l'avenir du code asynchrone dans JavaScrip t. Cela vous permettrait de gérer facilement vos opérations asynchrones et, par exemple, d'exécuter du code lorsque toutes ces opérations seraient terminées (si vous vouliez le faire).

+0

Le problème avec' forEach' dans cette approche est que vous ne pouvez pas attacher un rappel lorsque tous les éléments de la collection sont téléchargés. –

+0

@AlexandruOlaru Je ne vois pas pourquoi il serait moins possible d'attacher un tel rappel que toute autre approche typique (liaison, IIFE, générateur de fonctions). Est-ce que l'IIFE de votre réponse permet d'attacher un tel rappel d'une manière que '.forEach' ne fait pas? – JLRishe

+0

Alors, comment pouvez-vous joindre un rappel sur le résultat de '.forEach' de fonctions asynchrones? Vous devez implémenter une porte, ou simplement utiliser un outil existant comme 'async.each (data.foo, putOnS3, finalCallback)'. L'IIFE est une réponse à la première question, 'async.each' pour la deuxième question, où il y a 2 questions différentes. Accepter que 'bind' fonctionne aussi, pour le problème de portée. –

1
  1. Je ne comprends pas pourquoi, mais à l'intérieur de rappel passé à foo.method1 ... J'ai toujours la même valeur de v1 (je suppose que ce dernier est).

Vous obtenez le même v1 article parce que vous itérez fonctions asynchrones, donc internaly ce qu'il fait, il est votre for itération, puis fera x fermetures (data.foo.length) avec le dernier i value`

Alors, comment pouvez-vous résoudre ce problème? Vous pouvez envelopper votre i dans un IIFE afin que cela ne partage pas le contexte.

(function(i) { 
    var v1 = data.foo[i].name; 
    console.log("Loop: " + v1); 
    var params = { ... };    
    foo.method1(params, function(err, data1) { 
     console.log("Method1: " + v1); 
     s3.putObject(....); 
    }); 
})(i); 

Ce code va créer un champ spécial avec la valeur i appropriée.

La deuxième méthode consiste à utiliser let i au lieu de var i cela fera également la même astuce que le code ci-dessus. En savoir plus sur let

  1. Le contrôleur de code de la console Amazon me conseille qu'il est une mauvaise pratique pour créer une fonction dans une boucle

Pour résoudre ce problème, je vous recommande de regarder module async npm. Vous pouvez y trouver each.