Самый простой способ дождаться завершения некоторых асинхронных задач в 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 и использовать 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 выполняется асинхронно. Итак, если список объектов был длиннее трех, подробно описанных здесь, не могло ли быть так, что при оценке async.parallel (calls, function (err, result) вызовы еще не содержали всех функций из исходного списка?
Martin Beeby

5
@MartinBeeby forEachсинхронный. Посмотрите здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... Там же реализация forEachна дне. Не все с обратным вызовом асинхронно.
freakish

2
К слову, асинхронный режим также можно использовать в браузере.
Эрвин Весселс,

@MartinBeeby Все, что связано с обратным вызовом, ЯВЛЯЕТСЯ асинхронным, проблема в том, что forEach передается не «обратный вызов», а просто обычная функция (что является неправильным использованием терминологии Mozilla). На языке функционального программирования вы бы никогда не стали называть переданную функцию «обратным вызовом»

3
@ ghert85 Нет, с терминологией все в порядке. Обратный вызов - это просто любой исполняемый код, который передается в качестве аргумента другому коду и, как ожидается, будет выполнен в какой-то момент. Это стандартное определение. И он может вызываться синхронно или асинхронно. См. Это: en.wikipedia.org/wiki/Callback_(computer_programming)
причудливый

128

Используйте обещания .

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.


Предыдущий ответ (это предшествует встроенной поддержке Promises в Node):

Используйте обещания 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);

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

1
Обещания - это то, что нужно. Bluebird - еще одна библиотека обещаний, которая будет хорошо работать, если она находится в критически важном для производительности коде. Это должна быть прямая замена. Просто используйте require('bluebird').
weiyin 01

Я добавил пример Bluebird. Это немного отличается, поскольку лучший способ использовать Bluebird - это использовать эту promisifyAllфункцию.
Nate

Любая идея, как работает promisifyAll ... Я читал документы, но я не понимаю, что это то, как он обрабатывает функции, которые не имеют параметров, как function abc(data){, потому что это не похоже на function abc(err, callback){...В принципе, я не думаю, что все функции принимают ошибку как первый параметр и обратный вызов как второй параметр
Мухаммад Умер,

@MuhammadUmer Множество деталей на bluebirdjs.com/docs/api/promise.promisifyall.html
Нейт,

Прошло много времени с тех пор, как драйвер MongoDB также поддерживает обещания. Можете ли вы обновить свой пример, чтобы воспользоваться этим? .map(function(name) { return conn.collection(name).drop() })
djanowski

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

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


Возможно, это не так просто реализовать, но мне очень нравится получать ответ, не требующий внешних модулей. Спасибо!
counterbeing

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.