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

Понимая концепцию обратных вызовов javascript с помощью node.js, особенно в циклах

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

У меня в основном есть что-то, что вытаскивает JSON из API, создает новый, а затем делает что-то с этим. Очевидно, я не могу просто запустить его, потому что он запускает все сразу и имеет пустой JSON. В основном процессы должны выполняться последовательно, но если вытащить JSON из API, он может вытащить другой JSON, пока он ждет, тогда это нормально. Я просто запутался, поставив обратный вызов в цикле. Что мне делать с индексом? Я думаю, что видел некоторые места, которые используют обратные вызовы внутри цикла как своего рода рекурсивную функцию и вообще не используют для циклов.

Простые примеры помогут много.

4b9b3361

Ответ 1

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

function doSomething(callback) {
    callback();
}

Эта функция принимает ссылку на функцию обратного вызова, и все, что она делает, вызывает ее. Не очень интересно.: -)

Теперь позвольте использовать это в цикле:

var index;

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

(В вычислительно-интенсивном коде — как серверный процесс – – – –

Теперь, когда мы запустим это, мы увидим ожидаемый результат:

index = 0
index = 1
index = 2

Наш обратный вызов смог получить доступ к index, потому что обратный вызов является закрытием по данным в области, где он определен. (Не беспокойтесь о терминах "закрытие", закрытия не сложны.)

Причина, по которой я сказал, что, вероятно, лучше всего не делать этого выше в вычислительно-интенсивном производственном коде, заключается в том, что код создает функцию на каждой итерации (запрет на оптимизацию в компиляторе, а V8 очень умный, но оптимизирующий создание эти функции нетривиальны). Итак, вот немного переработанный пример:

var index;

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

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

Это может показаться немного удивительным, но оно по-прежнему работает одинаково и все равно имеет тот же результат, потому что doSomethingCallback по-прежнему является закрытием над index, поэтому он все еще видит значение index от когда он вызвал. Но теперь есть только одна функция doSomethingCallback, а не новая в каждом цикле.

Теперь давайте возьмем отрицательный пример, что работает не:

foo();

function foo() {
    var index;

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

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

Это не работает, потому что myCallback не определен в той же области (или вложенной области), в которой index находится в определенном порядке, и поэтому index есть undefined внутри myCallback.

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

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

Похоже, что приведенное выше должно работать так же, как и в наших предыдущих циклах, но есть решающее различие. В наших предыдущих циклах обратный вызов вызывался немедленно, и поэтому он видел правильное значение index, потому что index еще не успел двигаться дальше. В приведенном выше, однако, мы собираемся прокрутить цикл до вызова callback. Результат? Мы видим, что

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

Это важный момент. У закрытия нет копии данных, которые она закрывает, у нее есть живая ссылка на нее. Таким образом, к моменту завершения обратного вызова exit для каждого из этих процессов цикл уже будет завершен, поэтому все три вызова показывают одно и то же значение index (его значение на конец цикла).

Мы можем исправить это, если обратный вызов использует другую переменную, которая не изменится, например:

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

Теперь мы выводим правильные значения (в любом порядке, когда процессы выходят):

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

То, как это работает, заключается в том, что обратный вызов, который мы назначаем событию exit, закрывается по аргументу i в вызове, который мы делаем для makeExitCallback. Первый обратный вызов, который makeExitCallback создает и возвращает закрытие по значению i для этого вызова makeExitCallback, второй обратный вызов, который он создает, закрывается по значению i для этого вызова makeExitCallback (который отличается от i значение для более раннего вызова) и т.д.

Если вы дадите статью, указанную выше, прочитайте, что должно быть более четким. Терминология в статье немного устарела (ECMAScript 5 использует обновленную терминологию), но концепции не изменились.