JavaScriptで、いくつかの非同期タスクが完了するのを待つ最も簡単な方法は?


112

mongodbコレクションをいくつか削除したいのですが、それは非同期タスクです。コードは次のようになります。

var mongoose = require('mongoose');

mongoose.connect('mongo://localhost/xxx');

var conn = mongoose.connection;

['aaa','bbb','ccc'].forEach(function(name){
    conn.collection(name).drop(function(err) {
        console.log('dropped');
    });
});
console.log('all dropped');

コンソールに次のように表示されます。

all dropped
dropped
dropped
dropped

all droppedすべてのコレクションが削除された後に印刷されることを確認する最も簡単な方法は何ですか?サードパーティを使用してコードを簡略化できます。

回答:


92

あなたが使用しているmongooseので、サーバーサイドJavaScriptについて話しています。その場合、非同期モジュールを調べて使用することをお勧めしますasync.parallel(...)。このモジュールは非常に役立ちます-苦労している問題を解決するために開発されました。コードは次のようになります

var async = require('async');

var calls = [];

['aaa','bbb','ccc'].forEach(function(name){
    calls.push(function(callback) {
        conn.collection(name).drop(function(err) {
            if (err)
                return callback(err);
            console.log('dropped');
            callback(null, name);
        });
    }
)});

async.parallel(calls, function(err, result) {
    /* this code will run after all calls finished the job or
       when any of the calls passes an error */
    if (err)
        return console.log(err);
    console.log(result);
});

これで... forEachメソッドは非同期に発生します。したがって、オブジェクトリストがここで説明する3つよりも長い場合、async.parallel(calls、function(err、result)が評価されたときに、呼び出しにまだ元のリストのすべての関数が含まれていないのではないでしょうか?
Martin Beeby 2013年

5
@MartinBeeby forEachは同期です。こちらをご覧ください:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…forEach下部にの実装があります。コールバックのすべてが非同期であるとは限りません。
風変わりな

2
記録として、非同期はブラウザでも使用できます。
Erwin Wessels 2014年

@MartinBeebyコールバックのあるものはすべて非同期です。問題は、forEachに「コールバック」が渡されず、通常の関数(Mozillaによる用語の誤った使い方です)が渡されることです。関数型プログラミング言語では、「コールバック」渡された関数を呼び出すことはない

3
@ ghert85いいえ、用語に問題はありません。コールバックは、引数として他のコードに渡される実行可能コードであり、ある時点で実行されることが期待されています。それが標準の定義です。また、同期または非同期で呼び出すことができます。これを参照してください:en.wikipedia.org/wiki/Callback_(computer_programming)
風変わりな

128

Promiseを使用します。

var mongoose = require('mongoose');

mongoose.connect('your MongoDB connection string');
var conn = mongoose.connection;

var promises = ['aaa', 'bbb', 'ccc'].map(function(name) {
  return new Promise(function(resolve, reject) {
    var collection = conn.collection(name);
    collection.drop(function(err) {
      if (err) { return reject(err); }
      console.log('dropped ' + name);
      resolve();
    });
  });
});

Promise.all(promises)
.then(function() { console.log('all dropped)'); })
.catch(console.error);

これにより、各コレクションがドロップされ、各コレクションの後に「ドロップ」が出力され、完了したら「すべてドロップ」が出力されます。エラーが発生した場合はに表示されstderrます。


以前の回答(これはノードがPromiseをネイティブでサポートする以前のもの):

Q約束またはBluebird約束を使用します。

Q

var Q = require('q');
var mongoose = require('mongoose');

mongoose.connect('your MongoDB connection string');
var conn = mongoose.connection;

var promises = ['aaa','bbb','ccc'].map(function(name){
    var collection = conn.collection(name);
    return Q.ninvoke(collection, 'drop')
      .then(function() { console.log('dropped ' + name); });
});

Q.all(promises)
.then(function() { console.log('all dropped'); })
.fail(console.error);

ブルーバード

var Promise = require('bluebird');
var mongoose = Promise.promisifyAll(require('mongoose'));

mongoose.connect('your MongoDB connection string');
var conn = mongoose.connection;

var promises = ['aaa', 'bbb', 'ccc'].map(function(name) {
  return conn.collection(name).dropAsync().then(function() {
    console.log('dropped ' + name);
  });
});

Promise.all(promises)
.then(function() { console.log('all dropped'); })
.error(console.error);

1
約束は進むべき道です。Bluebirdは、これがパフォーマンスが重要なコードである場合にうまく機能するもう1つのプロミスライブラリです。それは、ドロップイン交換でなければなりません。だけを使用してくださいrequire('bluebird')
weiyin 2014

Bluebirdの例を追加しました。Bluebirdを使用する最良の方法はpromisifyAll機能を使用することなので、少し異なります。
ネイト

promisifyAll works..I'veは、ドキュメントを読んで、私はそれを得るいけないどのように任意のアイデアは、それがのようなパラメータをしない関数をどのように処理するかということであるfunction abc(data){、好きではないですので、function abc(err, callback){...基本的に私はすべての機能が第二のparamとして最初のパラメータとコールバックとしてエラーを取ると思ういけません
Muhammad Umer 2015

で、詳細の@MuhammadUmerたくさんbluebirdjs.com/docs/api/promise.promisifyall.html
ネイト

MongoDBドライバーがpromiseもサポートするようになって久しぶりです。これを利用するように例を更新できますか?.map(function(name) { return conn.collection(name).drop() })
djanowski 2016

21

これを行う方法は、共有カウンターを更新するコールバックをタスクに渡すことです。共有カウンターがゼロに達すると、すべてのタスクが終了したことがわかるので、通常のフローを続行できます。

var ntasks_left_to_go = 4;

var callback = function(){
    ntasks_left_to_go -= 1;
    if(ntasks_left_to_go <= 0){
         console.log('All tasks have completed. Do your stuff');
    }
}

task1(callback);
task2(callback);
task3(callback);
task4(callback);

もちろん、この種のコードをより汎用的または再利用可能にする多くの方法があり、そこにある多くの非同期プログラミングライブラリには、この種のことを行うための関数が少なくとも1つ必要です。


これは実装が最も簡単ではないかもしれませんが、外部モジュールを必要としない答えを見るのが本当に好きです。ありがとうございました!
対抗

8

@freakishの回答を拡張して、asyncはeachメソッドも提供します。これは、あなたのケースに特に適しているようです。

var async = require('async');

async.each(['aaa','bbb','ccc'], function(name, callback) {
    conn.collection(name).drop( callback );
}, function(err) {
    if( err ) { return console.log(err); }
    console.log('all dropped');
});

私見、これはコードをより効率的で読みやすくします。私は自由に削除しましたconsole.log('dropped')-あなたがそれを望むなら、代わりにこれを使ってください:

var async = require('async');

async.each(['aaa','bbb','ccc'], function(name, callback) {
    // if you really want the console.log( 'dropped' ),
    // replace the 'callback' here with an anonymous function
    conn.collection(name).drop( function(err) {
        if( err ) { return callback(err); }
        console.log('dropped');
        callback()
    });
}, function(err) {
    if( err ) { return console.log(err); }
    console.log('all dropped');
});

5

私は外部ライブラリなしでこれを行います:

var yourArray = ['aaa','bbb','ccc'];
var counter = [];

yourArray.forEach(function(name){
    conn.collection(name).drop(function(err) {
        counter.push(true);
        console.log('dropped');
        if(counter.length === yourArray.length){
            console.log('all dropped');
        }
    });                
});

4

すべての回答はかなり古いものです。2013年の初めから、Mongooseはすべてのクエリのプロミスを徐々にサポートし始めたので、これが、必要な順序で複数の非同期呼び出しを構造化するための推奨される方法になると思います。


0

deferred(別の約束/繰延実装)を行うことができます。

// Setup 'pdrop', promise version of 'drop' method
var deferred = require('deferred');
mongoose.Collection.prototype.pdrop =
    deferred.promisify(mongoose.Collection.prototype.drop);

// Drop collections:
deferred.map(['aaa','bbb','ccc'], function(name){
    return conn.collection(name).pdrop()(function () {
      console.log("dropped");
    });
}).end(function () {
    console.log("all dropped");
}, null);

0

Babelまたはそのようなトランスパイラーを使用していて、async / awaitを使用している場合は、次のようにすることができます。

function onDrop() {
   console.log("dropped");
}

async function dropAll( collections ) {
   const drops = collections.map(col => conn.collection(col).drop(onDrop) );
   await drops;
   console.log("all dropped");
}

コールバックを渡しdrop()てPromiseを返すことはできません。この例を修正して削除してもらえますonDropか?
djanowski
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.