2010-12-22 2 views
31

Je ne fais que commencer par node.js. J'ai fait un peu de trucs ajax mais rien de trop compliqué alors les rappels sont encore un peu au dessus de ma tête. J'ai regardé async, mais tout ce dont j'ai besoin est d'exécuter quelques fonctions séquentiellement.comprendre le concept des rappels javascript avec node.js, en particulier dans les boucles

J'ai fondamentalement quelque chose qui tire un peu de JSON d'une API, en crée une nouvelle, puis fait quelque chose avec ça. Évidemment, je ne peux pas l'exécuter simplement parce qu'il exécute tout à la fois et a un JSON vide. La plupart du temps, les processus doivent s'exécuter de manière séquentielle, mais si, tout en tirant JSON de l'API, il peut tirer d'autres JSON pendant qu'il attend, alors c'est très bien. Je suis juste confus en mettant le rappel en boucle. Qu'est-ce que je fais avec l'index? Je pense que j'ai vu des endroits qui utilisent des rappels à l'intérieur de la boucle comme une sorte de fonction récursive et que je n'utilise pas du tout pour les boucles.

Des exemples simples seraient très utiles.

+2

Je vous recommande de lire [JavaScript Closure Notes] (http://jibbering.com/faq/notes/closures/) - nodes.js joue juste selon les règles. –

+2

@pst: Enh, j'ai vu beaucoup mieux. Par exemple, * "Une fermeture est formée quand l'une de ces fonctions internes est rendue accessible en dehors de la fonction dans laquelle elle était contenue ..." * C'est faux. Une fermeture est créée lorsque vous créez la fonction, arrêt complet. Si la fonction peut être accédée en dehors de sa portée contenant est complètement hors de propos. Il commence aussi à parler de fermetures ayant des effets "potentiellement nocifs", ce qui n'est pas vrai pour le débutant. –

Répondre

85

Si le rappel est défini dans la même portée, la boucle est définie dans (ce qui est fréquemment le cas), puis le rappel aura accès à la variable d'index. En laissant de côté les détails de NodeJS un instant, considérons cette fonction:

function doSomething(callback) { 
    callback(); 
} 

Cette fonction accepte une référence de fonction de rappel et tout ce qu'il fait est l'appeler. Pas très excitant. :-)

Maintenant, nous allons utiliser que dans une boucle:

var index; 

for (index = 0; index < 3; ++index) { 
    doSomething(function() { 
     console.log("index = " + index); 
    }); 
} 

(Dans le code de calcul intensif   — comme un processus serveur   — préférable de ne pas faire littéralement ce qui précède dans le code de production, nous » ll y revenir dans un instant)

maintenant, quand nous courons, nous voyons le résultat attendu.

index = 0 
index = 1 
index = 2 

Notre rappel a été en mesure d'accéder à index, car le rappel est fermeture sur les données dans la portée où il est défini. (Ne vous inquiétez pas au sujet du terme « fermeture » closures are not complicated.)

La raison pour laquelle je l'ai dit est sans doute préférable de ne pas faire littéralement ce qui précède dans le code de production de calcul intensif est que le code crée une fonction sur chaque itération (sauf optimisation de fantaisie dans le compilateur, et V8 est très intelligent, mais l'optimisation de la création de ces fonctions est non-trivial). Voici donc un exemple un peu retravaillé:

var index; 

for (index = 0; index < 3; ++index) { 
    doSomething(doSomethingCallback); 
} 

function doSomethingCallback() { 
    console.log("index = " + index); 
} 

Cela peut paraître un peu surprenant, mais il fonctionne toujours de la même façon, et a toujours la même sortie, parce que doSomethingCallback est encore une fermeture sur index, il voit encore la valeur de index à partir de quand il s'appelle. Mais maintenant, il n'y a qu'une seule fonction doSomethingCallback, plutôt qu'une nouvelle sur chaque boucle.

Maintenant, nous allons prendre un exemple négatif, quelque chose qui ne le fait pas travail:

foo(); 

function foo() { 
    var index; 

    for (index = 0; index < 3; ++index) { 
     doSomething(myCallback); 
    } 
} 

function myCallback() { 
    console.log("index = " + index); // <== Error 
} 

qui échoue, car myCallback ne définit pas la même portée (ou une portée imbriquée) qui index est dans défini dans, et donc index est indéfini dans myCallback. Enfin, considérons la configuration des gestionnaires d'événements dans une boucle, car il faut faire attention à cela. Ici, nous allons plonger dans NodeJS un peu:

var spawn = require('child_process').spawn; 

var commands = [ 
    {cmd: 'ls', args: ['-lh', '/etc' ]}, 
    {cmd: 'ls', args: ['-lh', '/usr' ]}, 
    {cmd: 'ls', args: ['-lh', '/home']} 
]; 
var index, command, child; 

for (index = 0; index < commands.length; ++index) { 
    command = commands[index]; 
    child = spawn(command.cmd, command.args); 
    child.on('exit', function() { 
     console.log("Process index " + index + " exited"); // <== WRONG 
    }); 
} 

Il semble comme ci-dessus devraient travailler de la même façon que nos boucles antérieures ont fait, mais il y a une différence cruciale. Dans nos boucles antérieures, le rappel était appelé immédiatement, et il a donc vu la bonne valeur index car index n'avait pas encore eu l'occasion de passer à autre chose. Dans ce qui précède, cependant, nous allons faire tourner la boucle avant que le rappel ne soit appelé. Le résultat? Nous voyons

Process index 3 exited 
Process index 3 exited 
Process index 3 exited 

Ceci est un point crucial. Une fermeture n'a pas de copie des données qu'il ferme, elle a une référence . Ainsi, au moment où le rappel exit sur chacun de ces processus sera exécuté, la boucle sera déjà terminée, de sorte que tous les trois appels voient la même valeur index (sa valeur à partir de fin de la boucle).

Nous pouvons résoudre ce problème en ayant la fonction de rappel utiliser une variable différente qui ne changera pas, comme ceci:

var spawn = require('child_process').spawn; 

var commands = [ 
    {cmd: 'ls', args: ['-lh', '/etc' ]}, 
    {cmd: 'ls', args: ['-lh', '/usr' ]}, 
    {cmd: 'ls', args: ['-lh', '/home']} 
]; 
var index, command, child; 

for (index = 0; index < commands.length; ++index) { 
    command = commands[index]; 
    child = spawn(command.cmd, command.args); 
    child.on('exit', makeExitCallback(index)); 
} 

function makeExitCallback(i) { 
    return function() { 
     console.log("Process index " + i + " exited"); 
    }; 
} 

Maintenant, nous affichons les valeurs correctes (dans l'ordre la sortie des processus):

Process index 1 exited 
Process index 2 exited 
Process index 0 exited 

la façon dont fonctionne est que le rappel que nous attribuons à l'événement exit se ferme sur l'argument i dans l'appel que nous faisons makeExitCallback. Le premier rappel qui makeExitCallback crée et retourne se referme sur la valeur i pour cet appel à makeExitCallback, le deuxième rappel crée ferme sur la valeur i pour que appel à makeExitCallback (qui est différente de la valeur i pour l'appel précédent), etc.

Si vous donnez the article linked above une lecture, un certain nombre de choses devraient être plus claires. La terminologie de l'article est un peu datée (ECMAScript 5 utilise une terminologie mise à jour), mais les concepts n'ont pas changé.

+1

Super réponse :) Sympa. –

+0

petit bug dans le dernier exemple: 'pour (index = 0; index

+0

@Oliver: Merci, corrigé. –

Questions connexes