非同期コールバック関数のセットを待つにはどうすればよいですか?


95

私はJavaScriptでこのようなコードを持っています:

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

これらの非同期呼び出しがすべて完了したら、すべての配列の最小値を計算します。

それらのすべてをどのように待つことができますか?

私の現時点での唯一のアイデアは、doneと呼ばれるブール値の配列を用意し、i番目のコールバック関数でdone [i]をtrueに設定してから、whileと言うことです(すべてが完了しているわけではありません){}

編集:私は1つの可能性がありますが、醜い解決策は、各コールバックでdone配列を編集し、他のすべてのdoneが各コールバックから設定されている場合にメソッドを呼び出すことです。したがって、最後に完了するコールバックは継続するメソッドを呼び出します

前もって感謝します。


1
非同期とは、Ajaxリクエストが完了するのを待つという意味ですか?
Peter Aron Zentai 2012

6
注、動作while (not all are done) { }しません。待機中は、コールバックを実行できません。
cHao 2012

はい。外部APIへの非同期呼び出しが戻るのを待っているので、コールバックメソッドが呼び出されます。ええチャ​​オ、私はそれに気づきました、それが私がここで助けを求めている理由です:D
codersarepeople

あなたはこれを試すことができます:github.com/caolan/async非同期ユーティリティ関数の非常に素晴らしいセット。
ポールグレイソン

回答:


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呼び出しを行う方法に固有であるため、表示されていません)。1つの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トランスパイルまたはpromiseポリフィルの使用)がある場合、ES6で指定されたpromiseを使用できます。ブラウザのサポートについては、この表を参照してください。Promiseは、IEを除くほとんどすべての現在のブラウザーでサポートされています。

doAjax()がpromiseを返す場合、これを行うことができます:

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

Promise以外の非同期操作をPromiseを返すものにする必要がある場合は、次のように「Promisify」できます。

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

最近では、jqueryを必要としない、はるかにクリーンなソリューションがあります。私はFetchAPIとPromisesでそれをやっています
philx_x

@philx_x-IEとSafariのサポートについて何をしていますか?
jfriend00

@ jfriend00 githubがポリフィルgithub.com/github/fetchを作成しました。または、まだbabelがフェッチをサポートしているかどうかはわかりません。babeljs.io
philx_x

@philx_x-そう思いました。最近フェッチを使用するには、ポリフィルライブラリが必要です。ajaxライブラリーの回避についてのコメントから少し空気を取り除きます。Fetchはすばらしいですが、ポリフィルなしで使用できるようになるまでには数年かかります。すべてのブラウザーの最新バージョンでもまだです。ええ、それは本当に私の答えに何も変更しません。私が持っていたdoAjax()選択肢の一つとして約束を返すことを。と同じですfetch()
jfriend00

17

私たちは、今持っている:2015年からのチェックインネイティブの約束をして、最新のブラウザ(エッジ12は、Firefox 40、クロム43、サファリ8、オペラ座32とAndroidブラウザ4.4.4とiOSのSafari 8.4ではなく、Internet ExplorerやOpera Miniのと古いバージョンAndroidの)。

10個の非同期アクションを実行し、それらがすべて完了したときに通知を受け取りたい場合はPromise.all、外部ライブラリなしでネイティブを使用できます。

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

1
また、現在のバージョンのIEを含まない、使用可能なブラウザーを参照する必要がありますPromise.all()
jfriend00

10

jQueryのDeferredオブジェクトをwhenメソッドと共に使用できます。

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を使用できる、または使用したいとは限りません。SOに関するここでの習慣は、質問にjQueryのタグを付けるかどうかによってそれを示すことです。
jfriend00

4
$ .when呼び出しがこの例である場合は正しくありません。遅延/約束の配列を待つには、$。when.apply($、promises).then(function(){/ * do stuff * /})を使用する必要があります。
danw 2013

9

次のようにエミュレートできます:

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

その後、各非同期呼び出しはこれを行います:

countDownLatch.count++;

メソッドの最後で各非同期コールバック中に、次の行を追加します。

countDownLatch.check();

つまり、カウントダウンラッチ機能をエミュレートします。


すべてのユースケースの99%でPromiseが適していますが、Promiseポリフィルがそれを使用するJSよりも大きい状況で非同期コードを管理する方法を示しているため、この回答が好きです!
スキマスイッチ

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.