(JavaScript) Синхронизация forEach Loop с обратными вызовами внутри
Я делаю некоторые вычисления внутри двойного цикла forEach
Loop примерно так:
array.forEach(function(element){
Object.keys(element).forEach(function(key){
/* some complex computations with asynchronous callbacks */
});
});
someFunctionHere();
Есть ли способ, чтобы Loop завершил сначала, прежде чем выполнять функцию someFunctionHere( )
? или каким-либо образом программа узнает, заканчивается ли цикл, до перехода на someFunctionHere( )
...
Возможно, мне не хватает некоторых форумов, но те, которые я нашел, не помогли мне в том, чего я хочу достичь, и, кстати, я это делаю в NodeJS, также я спрашиваю, существуют ли существующие библиотеки, которые могут это сделать.
Я забыл добавить это или должен ли это быть другим вопросом?
Есть ли способ сделать итерацию синхронно, что она будет переходить только к следующей итерации после текущей итерации? (Извините за это)
Спасибо за любую помощь...
Ответы
Ответ 1
Взгляните на async.js и особенно на его поток управления, например each
whilst
и until
.
Используя async.js, вы можете получить то, что хотите.
В вашей реальной ситуации вам нужна функция each
(ранее известная как forEach
), соответственно функция eachSeries
, которая не запускает одиночные итерации параллельно, а последовательно (см. документация для каждого раздела для подробностей).
Чтобы привести пример:
async.eachSeries([ 2, 3, 5, 7, 11 ], function (prime, callback) {
console.log(prime);
callback(); // Alternatively: callback(new Error());
}, function (err) {
if (err) { throw err; }
console.log('Well done :-)!');
});
Это будет перебирать массив простых чисел и печатать их в правильном порядке один за другим и, наконец, распечатать Well done :-)!
.
Ответ 2
Вы можете обернуть свои обратные вызовы в обратном порядке:
var len = array.length;
function countdownWrapper(callback, callbackArgs) {
callback(callbackArgs);
if (--len == 0) {
someFunctionHere();
}
}
array.forEach(function(element){
Object.keys(element).forEach(function(key){
var wrappedCallback = countdownWrapper.bind(callback);
/* some complex computations with asynchronous WRAPPED callbacks */
});
});
Если обратные вызовы имеют разное количество аргументов, вы можете сделать небольшую операцию на arguments
вместо использования явного параметра callbackArgs
.
EDIT Ваше изменение поясняет, что вы хотите начать каждое сложное вычисление после того, как предыдущий расчет завершает обратный вызов. Это также может быть легко организовано затворами:
function complexOp(key, callback) { /* . . . */ }
function originalCallback(...) { /* . . . */ }
function doSomethingElse() { /* . . . */ }
var iteratorCallback = (function (body, callback, completion) {
var i = 0, len = array.length;
return function iterator() {
callback.apply(null, arguments);
if (++i < len) {
body(array[i], iterator);
} else {
completion();
}
};
}(complexOp, originalCallback, doSomethingElse));
// kick everything off:
complexOp(array[0], iteratorCallback);
Ответ 3
ВЫСОКОЭФФЕКТИВНОЕ РЕШЕНИЕ:
Может быть случай, когда вы захотите/разрешите обрабатывать массив асинхронно/параллельно, но хотите, чтобы функция вызывалась после того, как все члены forEach были обработаны.
Пример:
var async = require('async');
async.forEach(array,function(elementOfArray, callback){
//process each element of array in parallel
// ..
// ..
// ..
callback(); //notify that this iteration is complete
}, function(err){
if(err){throw err;}
console.log("processing all elements completed");
});
Следовательно, таким образом вы выполняете неблокирующие интенсивные операции ЦП.
ПРИМЕЧАНИЕ. Когда вы используете eachSeries для огромных массивов, многие итеративные обратные вызовы могут переполнять стек.
Читайте здесь:
https://github.com/caolan/async#control-flow
Ответ 4
Для браузеров, которые поддерживают Promise (или используя polyfill)/nodejs, я внедрил свою собственную версию синхронизации forEach с обратными вызовами, поэтому я просто расскажу об этом здесь.
Обратный вызов должен вернуть обещание.
function forEachSync(array, callback) {
let lastIndex = array.length - 1;
let startIndex = 0;
return new Promise((resolve, reject) => { // Finish all
let functionToIterateWith = (currIndex) => {
if (currIndex > lastIndex) {
return resolve();
} else {
callback(array[currIndex]).then(() => {
functionToIterateWith(currIndex + 1);
}).catch(err => reject(err));
}
}
functionToIterateWith(startIndex);
});
}