Передача данных в обратные вызовы node.js - асинхронная проблема

107
8

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

data = {first:  'someinfo',   second:  'somemoreinfo',   third:  'evenmoreinfo'};

for (var item in data) {
timeIntensiveFunction(item, data[item], function(err) {
if (!err) {
console.log(item + ' processed successfully');
} else {
console.log(item + ' failed');
}
});
}

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

first processed successfully
second processed successfully
third processed successfully

Вместо этого он покажет это, если первый доступ к базе данных занимает больше времени, чем цикл for:

third processed successfully
third processed successfully
third processed successfully

Это связано с тем, что ведение журнала консоли выполняется в timeIntensiveFunction(), который разумно будет вызываться только после завершения цикла for, поскольку timeIntensiveFunction() занимает так много времени. К моменту первого вызова обратного вызова item уже имеет последнее значение, third.

Как вы передаете "текущее" значение элемента в обратный вызов?

спросил(а) 2020-04-04T01:17:08+03:00 3 месяца назад
1
Решение
96

Если вы ищете библиотеку, чтобы упростить работу с асинхронными задачами, проверьте caolan/async.

var async = require("async");

var data = [{id: "first"}, {id: "second"}, {id: "third"}];

function timeIntensiveFunction(item, done) {
// do something
console.log("time intensive task started:", item.id);

// err?
// if (err) return done(err);

done();
}

function processItem(item, done) {
timeIntensiveFunction(item, function(err) {
if (err) return done(err);
console.log("task complete:", item.id);
done();
});
}

async.map(data, processItem);

Вывод

time intensive task started: first
task complete: first
time intensive task started: second
task complete: second
time intensive task started: third
task complete: third

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

ответил(а) 2020-04-04T01:35:58.541220+03:00 3 месяца назад
39

Проблема в том, что он вызывает обратный вызов только с последним.

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

var printStatus = function(item){
return function(err) {
if (!err) {
console.log(item + ' processed successfully');
} else {
console.log(item + ' failed');
}
}
}

for (var item in data) {
timeIntensiveFunction(item, data[item], printStatus(item));
}

ответил(а) 2020-04-04T01:17:08+03:00 3 месяца назад
40

Это обычная "gotcha" с закрытием в javascript. Один из способов заключается в том, чтобы обернуть вызов функции анонимной функцией и перетащить item. Вот так:

for (var item in data) {
(function(item){
timeIntensiveFunction(item, data[item], function(err) {
if (!err) {
console.log(item + ' processed successfully');
} else {
console.log(item + ' failed');
}
});
})(item);
}

ответил(а) 2020-04-04T01:17:08+03:00 3 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема