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