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



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



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



[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!
620   13  

13 ответов:

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 и другим) этот подход гарантирует, что все элементы обрабатываются перед вызовом обратного вызова "done". Подход, который предлагает Эмиль, хотя он обычно эффективен в моем опыте, не дает такой же гарантии.

использование 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();

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

например:

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

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

надеюсь, что это помогает.

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

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

надеюсь, что это исправит вашу проблему, я обычно работаю с этим, когда мне нужно выполнить 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();
}

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

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>

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

Это решение для узла.js, который является асинхронным.

использование асинхронного пакета npm.

(JavaScript) синхронизация цикла forEach с обратными вызовами внутри

Как насчет 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);

мое решение:

//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

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

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. Это может заменить каждую функцию или код вы хотите. Гудлак

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

простое решение было бы похоже на follow

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

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

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

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

Comments

    Ничего не найдено.