Самый простой способ дождаться завершения некоторых асинхронных задач в Javascript?

Я хочу удалить некоторые коллекции 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 будет напечатан после того, как все коллекции будут удалены? Для упрощения кода может использоваться любая сторонняя сторона.

Ответ 1

Я вижу, что вы используете 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);
});

Ответ 2

Используйте Promises.

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.


Предыдущий ответ (эта предварительная дата Node s встроенная поддержка для Promises):

Используйте Q promises или Bluebird promises.

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

С Bluebird:

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

Ответ 3

Способ сделать это - передать задачам обратный вызов, который обновляет общий счетчик. Когда общий счетчик достигает нуля, вы знаете, что все задачи завершены, чтобы вы могли продолжить свой обычный поток.

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

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

Ответ 4

Расширяясь при ответе @freakish, async также предлагает каждый метод, который кажется особенно подходящим для вашего случая:

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

IMHO, это делает код более эффективным и понятным. Я допустил удаление 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

Я делаю это без внешних libaries:

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

Ответ 6

Все ответы довольно старые. С начала 2013 года Mongoose начал поддерживать promises постепенно для всех запросов, так что это был бы рекомендуемый способ структурирования нескольких асинхронных вызовов в требуемый порядок в будущем, я думаю.

Ответ 7

Если вы используете Babel или такие транспиляторы и используете async/wait, вы можете сделать:

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

Ответ 8

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