Найпростіший спосіб чекати завершення деяких асинхронних завдань у 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 (виклики, функція (помилка, результат)) ще не містять усіх функцій у вихідному списку?
Мартін Бібі

5
@MartinBeeby forEachє синхронним. Подивіться тут: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…forEach Внизу є реалізація . Не все з зворотним викликом є ​​асинхронним.
каверзний

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.


Попередня відповідь (це попередня підтримка Node для Promises):

Використовуйте обіцянки 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

Я додав приклад Bluebird. Трохи інакше, оскільки найкращим способом використання Bluebird є використання promisifyAllфункції.
Нейт

Будь-яка ідея, як працює promisifyAll. Я читав документи, але я не розумію, що це обробка функцій, які не відповідають параметрам function abc(data){, тому що це не так, як в function abc(err, callback){...основному, я не думаю, що всі функції помиляються як перший парам і зворотний виклик як 2-й парам
Мухаммед Умер

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

Звичайно, існує багато способів зробити такий вид коду більш загальним або багаторазовим, і будь-яка з багатьох бібліотек програмування для асинхронізації там повинна мати принаймні одну функцію для виконання подібних речей.


Це може бути не найпростішим у здійсненні, але мені дуже подобається бачити відповідь, яка не вимагає зовнішніх модулів. Дякую!
протидія

8

Розгортаючись на @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

Я роблю це без зовнішніх бібліотек:

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

Ви не можете передати зворотний дзвінок drop()і очікувати повернення Обіцянки. Чи можете ви виправити цей приклад і видалитиonDrop ?
djanowski
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.