모든 비동기 forEach 콜백이 완료된 후의 콜백


245

제목에서 알 수 있듯이 어떻게해야합니까?

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!

13
표준 배열 forEach메소드에 done콜백 매개 변수와 allDone콜백 이 있으면 좋을 것입니다 !
Vanuan

22
자바 스크립트에서 너무 많은 레슬링이 필요한 단순한 수치입니다.
Ali

답변:


410

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

31
그러나 forEach 내부에 비동기 항목이있는 경우 (예 : URL 배열을 반복하고 HTTP GET을 수행하는 경우) res.end가 마지막으로 호출된다는 보장은 없습니다.
AlexMA

루프에서 비동기 작업이 수행 된 후 콜백을 발생시키기 위해 비동기 유틸리티의 각 메소드를 사용할 수 있습니다. github.com/caolan/async#each
elkelk

2
내가 내 대답을 업데이 트했습니다 @Vanuan 더 나은 다소의 편집 : 일치
닉 톰린

4
왜 그냥 if(index === array.length - 1)제거 하지itemsProcessed
Amin Jafari

5
@AminJafari 비동기 호출은 등록 된 정확한 순서대로 해결되지 않을 수 있기 때문에 (서버에 호출하는 중이고 두 번째 호출에서 약간 멈춰 있지만 마지막 호출은 잘 처리됩니다). 마지막 비동기 호출은 이전 호출보다 먼저 해결할 수 있습니다. 모든 콜백은 해결 순서에 관계없이 실행되어야 하므로 카운터 뮤트를 방지 합니다.
Nick Tomlin 2018

25

비동기 함수가 발생하고 코드를 실행하기 전에 작업이 완료되도록하려면 항상 콜백 기능을 사용할 수 있는지 확인하십시오.

예를 들면 다음과 같습니다.

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

참고 : 각 functionAfterForEach작업이 완료된 후 실행되는 기능입니다. asynchronousforeach 내에서 실행되는 비동기 함수입니다.


9
비동기 요청의 실행 순서가 보장되지 않기 때문에 작동하지 않습니다. 마지막 비동기 요청은 다른 요청보다 먼저 종료되고 모든 요청이 완료되기 전에 functionAfterForEach ()를 실행할 수 있습니다.
Rémy DAVID

@ RémyDAVID 그렇습니다. 실행 순서에 관한 요점이 있거나 프로세스가 얼마나 오래 완료되었는지 말해야합니까, 자바 스크립트는 단일 스레드이므로 결국 작동합니다. 그리고 증거는이 답변이받은 찬성입니다.
Emil Reña Enriquez

1
나는 왜 당신이 많은 투표를했는지 잘 모르겠지만, 레미는 맞습니다. 비동기식이란 요청이 언제든지 반환 될 수 있으므로 코드가 전혀 작동하지 않습니다. JavaScript는 멀티 스레드가 아니지만 브라우저입니다. 크게 추가 할 수 있습니다. 따라서 서버로부터 응답을받는 시점에 따라 어떤 순서로든 언제든지 콜백 중 하나를 호출 할 수 있습니다 ...
Alexis Wilke

2
예, 이것은 대답이 완전히 잘못되었습니다. 병렬로 10 회 다운로드를 실행하면 마지막 다운로드가 나머지 다운로드보다 먼저 완료되므로 실행이 종료됩니다.
knrdk

카운터를 사용하여 완료된 비동기 작업 수를 늘리고 인덱스 대신 배열의 길이와 일치시키는 것이 좋습니다. 공감의 수는 답의 정확성을 증명하는 것과 아무 관련이 없습니다.
Alex

17

이것이 문제를 해결하기를 바랍니다. 내가 비동기 작업으로 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();
}

나는 Angular 9 코드에서 비슷한 문제를 겪고 있었고이 대답은 나를 위해 속임수를 사용했습니다. @Emil Reña Enriquez 답변도 저에게 효과적이지만이 문제에 대한보다 정확하고 간단한 답변이라고 생각합니다.
omostan

17

비동기식에 몇 개의 오답이 주어 졌는지는 이상합니다. 사례에 ! 인덱스 검사가 예상되는 동작을 제공하지 않는다는 것을 간단히 나타낼 수 있습니다.

// 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첫 번째 요소가 아직 보류중인 동안 첫 번째 반복이 완료되면 콜백이 호출됩니다!

비동기와 같은 외부 라이브러리를 사용하지 않고이 문제를 해결하려면 각 반복 후에 목록의 길이를 줄이고 감소시키는 것이 가장 좋습니다. 스레드가 하나뿐이므로 경쟁 조건이 없을 것입니다.

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

1
아마도 유일한 해결책 일 것입니다. 비동기 라이브러리도 카운터를 사용합니까?
Vanuan

1
다른 솔루션이 효과가 있지만 체인을 추가하거나 복잡성을 추가 할 필요가 없기 때문에 가장 매력적입니다. KISS
azatar

배열 길이가 0 인 상황도 고려하십시오.이 경우 콜백은 호출되지 않습니다
Saeed Ir

6

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

1
노드 v10의 Availabe
Matt Swezey

2

Promise가없는 솔루션 (다음 작업이 시작되기 전에 모든 작업이 종료되도록 보장) :

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>


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

1
foreach 내부에서 비동기 작업을 수행하면 작동하지 않습니다.
Sudhanshu Gaur


0

내 해결책 :

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

해결책은 혁신적이지만 오류가옵니다- "작업은 기능이 아닙니다"
Genius

0

나는 그것을 해결하기 위해 쉬운 방법을 시도하고, 당신과 그것을 공유하십시오 :

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노드 js에서 mssql 라이브러리의 기능입니다. 이것은 각 기능이나 원하는 코드를 대체 할 수 있습니다. 행운을 빕니다


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

-2

목록을 반복하기 위해 콜백이 필요하지 않습니다. end()루프 후 호출을 추가하십시오 .

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

3
OP는 비동기 로직이 각 반복에 대해 실행될 것이라고 강조했습니다. res.write비동기 작업이 아니므로 코드가 작동하지 않습니다.
Jim G.

-2

간단한 해결책은 다음과 같습니다.

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

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

3
질문의 전제 인 비동기 코드에서는 작동하지 않습니다.
grg

-3

전체 반복 횟수를 확인하기 위해 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);

이것은 논리적으로 단순 해 보입니다
Zeal Murapa
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.