Обратный вызов после всех асинхронных для всех обратных вызовов завершен

Как следует из названия. Как мне это сделать?

Я хочу вызвать whenAllDone() после того, как forEach-loop прошел через каждый элемент и выполнил некоторую асинхронную обработку.

[1, 2, 3].forEach(
  function(item, index, array, done) {
     asyncFunction(item, function itemDone() {
       console.log(item + " done");
       done();
     });
  }, function allDone() {
     console.log("All done");
     whenAllDone();
  }
);

Можно заставить его работать так? Когда второй аргумент forEach является функцией обратного вызова, которая запускается после того, как она прошла через все итерации?

Ожидаемый результат:

3 done
1 done
2 done
All done!

Ответ 1

Array.forEach не предоставляет такой тонкости (о, если бы это было), но есть несколько способов выполнить то, что вы хотите:

Используя простой счетчик

function callback () { console.log('all done'); }

var itemsProcessed = 0;

[1, 2, 3].forEach((item, index, array) => {
  asyncFunction(item, () => {
    itemsProcessed++;
    if(itemsProcessed === array.length) {
      callback();
    }
  });
});

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

Использование обещаний ES6

(библиотека обещаний может использоваться для старых браузеров):

  1. Обработка всех запросов, гарантирующих синхронное выполнение (например, 1, затем 2, затем 3).

    function asyncFunction (item, cb) {
      setTimeout(() => {
        console.log('done with', item);
        cb();
      }, 100);
    }
    
    let requests = [1, 2, 3].reduce((promiseChain, item) => {
        return promiseChain.then(() => new Promise((resolve) => {
          asyncFunction(item, resolve);
        }));
    }, Promise.resolve());
    
    requests.then(() => console.log('done'))
    
  2. Обрабатывать все асинхронные запросы без "синхронного" выполнения (2 могут закончиться быстрее, чем 1)

    let requests = [1,2,3].map((item) => {
        return new Promise((resolve) => {
          asyncFunction(item, resolve);
        });
    })
    
    Promise.all(requests).then(() => console.log('done'));
    

Использование асинхронной библиотеки

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

редактировать

Основная часть вопроса была отредактирована для удаления ранее синхронного примера кода, поэтому я уточнил свой ответ, чтобы уточнить. В исходном примере для моделирования асинхронного поведения использовался синхронный код, поэтому применялось следующее:

array.forEach является синхронным, также как и res.write, так что вы можете просто поставить обратный вызов после вызова foreach:

  posts.foreach(function(v, i) {
    res.write(v + ". index " + i);
  });

  res.end();

Ответ 2

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

Например:

var ctr = 0;
posts.forEach(function(element, index, array){
    asynchronous(function(data){
         ctr++; 
         if (ctr === array.length) {
             functionAfterForEach();
         }
    })
});

Примечание: functionAfterForEach - это функция, которая должна быть выполнена после выполнения задач foreach. asynchronous - это асинхронная функция, выполняемая внутри foreach.

Ответ 3

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

// INCORRECT
var list = [4000, 2000];
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
    }, l);
});

выход:

4000 started
2000 started
1: 2000
0: 4000

Если мы проверим index === array.length - 1, обратный вызов будет вызван по завершении первой итерации, пока первый элемент все еще ожидает!

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

var list = [4000, 2000];
var counter = list.length;
list.forEach(function(l, index) {
    console.log(l + ' started ...');
    setTimeout(function() {
        console.log(index + ': ' + l);
        counter -= 1;
        if ( counter === 0)
            // call your callback here
    }, l);
});

Ответ 4

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

foo = [a,b,c,d];
waiting = foo.length;
foo.forEach(function(entry){
      doAsynchronousFunction(entry,finish) //call finish after each entry
}
function finish(){
      waiting--;
      if (waiting==0) {
          //do your Job intended to be done after forEach is completed
      } 
}

с

function doAsynchronousFunction(entry,callback){
       //asynchronousjob with entry
       callback();
}

Ответ 5

С ES2018 вы можете использовать асинхронные итераторы:

const asyncFunction = a => fetch(a);
const itemDone = a => console.log(a);

async function example() {
  const arrayOfFetchPromises = [1, 2, 3].map(asyncFunction);

  for await (const item of arrayOfFetchPromises) {
    itemDone(item);
  }

  console.log('All done');
}

Ответ 6

Мое решение без обещания (это гарантирует, что каждое действие заканчивается до следующего):

Array.prototype.forEachAsync = function (callback, end) {
        var self = this;
    
        function task(index) {
            var x = self[index];
            if (index >= self.length) {
                end()
            }
            else {
                callback(self[index], index, self, function () {
                    task(index + 1);
                });
            }
        }
    
        task(0);
    };
    
    
    var i = 0;
    var myArray = Array.apply(null, Array(10)).map(function(item) { return i++; });
    console.log(JSON.stringify(myArray));
    myArray.forEachAsync(function(item, index, arr, next){
      setTimeout(function(){
        $(".toto").append("<div>item index " + item + " done</div>");
        console.log("action " + item + " done");
        next();
      }, 300);
    }, function(){
        $(".toto").append("<div>ALL ACTIONS ARE DONE</div>");
        console.log("ALL ACTIONS ARE DONE");
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="toto">

</div>

Ответ 7

 var counter = 0;
 var listArray = [0, 1, 2, 3, 4];
 function callBack() {
     if (listArray.length === counter) {
         console.log('All Done')
     }
 };
 listArray.forEach(function(element){
     console.log(element);
     counter = counter + 1;
     callBack();
 });

Ответ 9

Мое решение:

//Object forEachDone

Object.defineProperty(Array.prototype, "forEachDone", {
    enumerable: false,
    value: function(task, cb){
        var counter = 0;
        this.forEach(function(item, index, array){
            task(item, index, array);
            if(array.length === ++counter){
                if(cb) cb();
            }
        });
    }
});


//Array forEachDone

Object.defineProperty(Object.prototype, "forEachDone", {
    enumerable: false,
    value: function(task, cb){
        var obj = this;
        var counter = 0;
        Object.keys(obj).forEach(function(key, index, array){
            task(obj[key], key, obj);
            if(array.length === ++counter){
                if(cb) cb();
            }
        });
    }
});

Пример:

var arr = ['a', 'b', 'c'];

arr.forEachDone(function(item){
    console.log(item);
}, function(){
   console.log('done');
});

// out: a b c done

Ответ 10

Я пробую Easy Way, чтобы решить эту проблему, поделюсь с вами:

let counter = 0;
            arr.forEach(async (item, index) => {
                await request.query(item, (err, recordset) => {
                    if (err) console.log(err);

                    //do Somthings

                    counter++;
                    if(counter == tableCmd.length){
                        sql.close();
                        callback();
                    }
                });

request является функцией библиотеки mssql в узле js. Это может заменить каждую функцию или код, который вы хотите. Удачи

Ответ 11

var i=0;
const waitFor = (ms) => 
{ 
  new Promise((r) => 
  {
   setTimeout(function () {
   console.log('timeout completed: ',ms,' : ',i); 
     i++;
     if(i==data.length){
      console.log('Done')  
    }
  }, ms); 
 })
}
var data=[1000, 200, 500];
data.forEach((num) => {
  waitFor(num)
})

Ответ 12

Как насчет setInterval, чтобы проверить полный счетчик итераций, приносит гарантию. не уверен, что он не будет перегружать область видимости, но я использую ее и, кажется, один

_.forEach(actual_JSON, function (key, value) {

     // run any action and push with each iteration 

     array.push(response.id)

});


setInterval(function(){

    if(array.length > 300) {

        callback()

    }

}, 100);

Ответ 13

Простое решение будет как следовать

function callback(){console.log("i am done");}

["a", "b", "c"].forEach(function(item, index, array){
    //code here
    if(i == array.length -1)
    callback()
}

Ответ 14

Вам не требуется обратный вызов для повтора через список. Просто добавьте вызов end() после цикла.

posts.forEach(function(v, i){
   res.write(v + ". Index " + i);
});
res.end();