비동기 콜백 함수 세트를 어떻게 기다릴 수 있습니까?


95

자바 스크립트에서 다음과 같은 코드가 있습니다.

forloop {
    //async call, returns an array to its callback
}

이러한 비동기 호출이 모두 완료된 후 모든 배열에 대한 최소값을 계산하고 싶습니다.

그들 모두를 어떻게 기다릴 수 있습니까?

지금 내 유일한 아이디어는 done이라는 부울 배열을 만들고 i 번째 콜백 함수에서 done [i]를 true로 설정 한 다음 while (모두 완료되지 않음) {}

편집 : 가능하지만 추악한 해결책은 각 콜백에서 done 배열을 편집 한 다음 각 콜백에서 다른 모든 완료가 설정된 경우 메서드를 호출하는 것이므로 완료 할 마지막 콜백은 계속되는 메서드를 호출합니다.

미리 감사드립니다.


1
비동기에서 Ajax 요청이 완료되기를 기다리는 것을 의미합니까?
Peter Aron Zentai

6
참고 while (not all are done) { }작동하지 않을 것입니다. 바쁜 대기 중에는 콜백을 실행할 수 없습니다.
cHao

예. 콜백 메서드를 실행하도록 외부 API에 대한 비동기 호출이 반환되기를 기다리고 있습니다. D를 : 차오 그래, 내가 여기에 도움을 부탁 해요 이유입니다, 그 실현
codersarepeople

시도해 볼 수 있습니다 : github.com/caolan/async 아주 멋진 비동기 유틸리티 함수 세트입니다.
Paul Greyson

답변:


191

코드에 대해 구체적이지 않았으므로 시나리오를 작성하겠습니다. 10 개의 ajax 호출이 있고 그 10 개의 ajax 호출의 결과를 누적하고 싶은데 모두 완료되면 무언가를하고 싶다고 가정 해 보겠습니다. 배열에 데이터를 축적하고 마지막 항목이 언제 완료되었는지 추적하여 이렇게 할 수 있습니다.

수동 카운터

var ajaxCallsRemaining = 10;
var returnedData = [];

for (var i = 0; i < 10; i++) {
    doAjax(whatever, function(response) {
        // success handler from the ajax call

        // save response
        returnedData.push(response);

        // see if we're done with the last ajax call
        --ajaxCallsRemaining;
        if (ajaxCallsRemaining <= 0) {
            // all data is here now
            // look through the returnedData and do whatever processing 
            // you want on it right here
        }
    });
}

참고 : 여기에서 오류 처리가 중요합니다 (Ajax 호출 방법에 따라 다르기 때문에 표시되지 않음). 하나의 ajax 호출이 오류로 인해 완료되지 않거나 오랜 시간 동안 멈춰 있거나 오랜 시간 후에 시간 초과되는 경우를 어떻게 처리 할 것인지 생각하고 싶을 것입니다.


jQuery 약속

2014 년 제 답변에 추가합니다. 요즘에는 jQuery가 $.ajax()이미 약속을 반환하고 약속 $.when()그룹이 모두 해결되고 반환 결과를 수집 할 때 알려주기 때문에 이러한 유형의 문제를 해결하는 데 약속이 자주 사용됩니다 .

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push($.ajax(...));
}
$.when.apply($, promises).then(function() {
    // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
    // you can process it here
}, function() {
    // error occurred
});

ES6 표준 약속

kba의 답변에 지정된대로 : 네이티브 promise가 내장 된 환경 (최신 브라우저 또는 node.js 또는 babeljs transpile 사용 또는 promise polyfill 사용)이있는 경우 ES6 지정 약속을 사용할 수 있습니다. 브라우저 지원 은 이 표 를 참조하십시오 . 약속은 IE를 제외한 거의 모든 현재 브라우저에서 지원됩니다.

경우 doAjax()약속 반환, 당신은이 작업을 수행 할 수 있습니다

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

약속이없는 비동기 작업을 약속을 반환하는 작업으로 만들어야하는 경우 다음과 같이 "약속"할 수 있습니다.

function doAjax(...) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(..., function(err, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}

그런 다음 위의 패턴을 사용하십시오.

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

블루 버드 약속

Bluebird promise 라이브러리와 같이 기능이 더 풍부한 라이브러리를 사용하는 경우 이를 쉽게 수행 할 수 있도록 몇 가지 추가 기능이 내장되어 있습니다.

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });

4
@kba-특히 Ajax 용 jQuery를 이미 사용하고있는 경우 모든 기술이 여전히 적용 가능하기 때문에이 답변을 구식이라고 부르지 않았을 것입니다. 하지만 네이티브 약속을 포함하기 위해 여러 가지 방법으로 업데이트했습니다.
jfriend00 2015

요즘에는 jquery가 필요하지 않은 훨씬 더 깨끗한 솔루션이 있습니다. 나는 FetchAPI과 약속을하고 있어요
philx_x

@philx_x-IE 및 Safari 지원에 대해 무엇을하고 있습니까?
jfriend00 2016-04-14

@ jfriend00 github가 polyfill github.com/github/fetch를 만들었 습니다 . 아니면 babel이 아직 fetch를 지원하는지 잘 모르겠습니다. babeljs.io
philx_x apr

@philx_x-그렇게 생각합니다. 요즘 fetch를 사용하려면 polyfill 라이브러리가 필요합니다. ajax 라이브러리를 피하는 것에 대한 귀하의 의견에서 약간의 공기를 빼앗습니다. Fetch는 좋지만 폴리 필없이 사용할 수있는 데는 몇 년이 걸립니다. 아직 모든 브라우저의 최신 버전이 아닙니다. 해, 그것은 내 대답에서 실제로 아무것도 변경하지 않습니다. 나는 doAjax()옵션 중 하나로서 약속을 돌려주는 것을 가지고 있었다 . 동일 것 fetch().
jfriend00 2016-04-14

17

2015 년부터 체크인 : 이제 최신 브라우저 (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 및 Android 브라우저 4.4.4 및 iOS Safari 8.4 에서 기본 약속 이 있지만 Internet Explorer, Opera Mini 및 이전 버전은 제외됨) 안드로이드).

10 개의 비동기 작업을 수행하고 작업이 모두 완료되었을 때 알림을 받으려면 Promise.all외부 라이브러리없이 native를 사용할 수 있습니다 .

function asyncAction(i) {
    return new Promise(function(resolve, reject) {
        var result = calculateResult();
        if (result.hasError()) {
            return reject(result.error);
        }
        return resolve(result);
    });
}

var promises = [];
for (var i=0; i < 10; i++) {
    promises.push(asyncAction(i));
}

Promise.all(promises).then(function AcceptHandler(results) {
    handleResults(results),
}, function ErrorHandler(error) {
    handleError(error);
});

2
Promises.all()이어야합니다 Promise.all().
jfriend00 2015

1
귀하의 답변은 현재 버전의 IE를 포함하지 않는 브라우저Promise.all() 에서 사용할 수 있는지도 참조해야합니다 .
jfriend00 2015

10

when 메소드 와 함께 jQuery의 Deferred 객체를 사용할 수 있습니다 .

deferredArray = [];
forloop {
    deferred = new $.Deferred();
    ajaxCall(function() {
      deferred.resolve();
    }
    deferredArray.push(deferred);
}

$.when(deferredArray, function() {
  //this code is called after all the ajax calls are done
});

7
질문에는 jQuery일반적으로 OP가 jQuery 답변을 원하지 않았 음을 의미하는 태그가 지정 되지 않았습니다.
jfriend00

8
@ jfriend00 jQuery에서 이미 만들어 졌을 때 바퀴를 재발 명하고 싶지 않았습니다
Paul

4
@Paul 그래서 차라리 40kb의 정크를 포함하여 휠을 다시 발명하여 간단한 작업을 수행합니다 (지연됨)
Raynos

2
그러나 모든 사람이 jQuery를 사용할 수 있거나 사용하고 싶지는 않으며 여기에서 사용자 정의는 jQuery로 질문에 태그를 지정하는지 여부를 표시하는 것입니다.
jfriend00

4
$ .when 호출이이 예는 올바르지 않습니다. 지연된 / 약속의 배열을 기다리려면 $ .when.apply ($, promises) .then (function () {/ * do stuff * /})를 사용해야합니다.
danw

9

다음과 같이 에뮬레이션 할 수 있습니다.

  countDownLatch = {
     count: 0,
     check: function() {
         this.count--;
         if (this.count == 0) this.calculate();
     },
     calculate: function() {...}
  };

각 비동기 호출은 다음을 수행합니다.

countDownLatch.count++;

메서드의 끝에서 각 비동기 호출에서 다음 줄을 추가합니다.

countDownLatch.check();

즉, 카운트 다운 래치 기능을 에뮬레이트합니다.


모든 사용 사례의 99 %에서 Promise가 갈 길이지만 Promise polyfill이 그것을 사용하는 JS보다 큰 상황에서 비동기 코드를 관리하는 방법을 보여주기 때문에이 답변이 마음에 듭니다!
Sukima

6

이것은 제 생각에 가장 깔끔한 방법입니다.

Promise.all

FetchAPI

(어떤 이유로 Array.map이 .then 내부에서 작동하지 않습니다.하지만 .forEach 및 [] .concat () 또는 이와 유사한 것을 사용할 수 있습니다)

Promise.all([
  fetch('/user/4'),
  fetch('/user/5'),
  fetch('/user/6'),
  fetch('/user/7'),
  fetch('/user/8')
]).then(responses => {
  return responses.map(response => {response.json()})
}).then((values) => {
  console.log(values);
})

1
나는이 할 필요가 생각 return responses.map(response => { return response.json(); }), 또는 return responses.map(response => response.json()).

1

다음과 같은 제어 흐름 라이브러리를 사용하십시오. after

after.map(array, function (value, done) {
    // do something async
    setTimeout(function () {
        // do something with the value
        done(null, value * 2)
    }, 10)
}, function (err, mappedArray) {
    // all done, continue here
    console.log(mappedArray)
})
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.