Najprostszy sposób na czekanie na zakończenie niektórych zadań asynchronicznych w JavaScript?

112

Chcę usunąć niektóre kolekcje mongodb, ale jest to zadanie asynchroniczne. Kod będzie wyglądał następująco:

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

Konsola wyświetla:

all dropped
dropped
dropped
dropped

Jaki jest najprostszy sposób, aby upewnić się, że all droppedzostanie wydrukowany po upuszczeniu wszystkich kolekcji? Aby uprościć kod, można użyć dowolnej firmy zewnętrznej.

Freewind
źródło

Odpowiedzi:

92

Widzę, że używasz, mongoosewięc mówisz o JavaScript po stronie serwera. W takim razie radzę spojrzeć na moduł asynchroniczny i użyć async.parallel(...). Ten moduł okaże się bardzo pomocny - został opracowany, aby rozwiązać problem, z którym się borykasz. Twój kod może wyglądać tak

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);
});
kapryśny
źródło
Dzięki temu ... metoda forEach działa asynchronicznie. Więc jeśli lista obiektów byłaby dłuższa niż wyszczególniona tutaj 3, czy nie może być tak, że gdy async.parallel (wywołania, funkcja (błąd, wynik) jest oceniana, wywołania nie zawierają jeszcze wszystkich funkcji z oryginalnej listy?
Martin Beeby,
5
@MartinBeeby forEachjest synchroniczny. Zajrzyj tutaj: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... Na forEachdole jest implementacja . Nie wszystko z callbackiem jest asynchroniczne.
dziwaczny
2
Dla przypomnienia, async może być również używany w przeglądarce.
Erwin Wessels
@MartinBeeby Wszystko z wywołaniem zwrotnym JEST asynchroniczne, problem polega na tym, że forEach nie otrzymuje „wywołania zwrotnego”, ale zwykłą funkcję (co jest niewłaściwym użyciem terminologii przez Mozillę). W funkcjonalnym języku programowania nigdy nie nazwałbyś przekazanej funkcji „oddzwonieniem”
3
@ ghert85 Nie, nie ma nic złego w terminologii. Callback to po prostu dowolny wykonywalny kod, który jest przekazywany jako argument do innego kodu i oczekuje się, że zostanie wykonany w pewnym momencie. To standardowa definicja. Można go wywołać synchronicznie lub asynchronicznie. Zobacz to: en.wikipedia.org/wiki/Callback_(computer_programming)
dziwaczny
128

Użyj obietnic .

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

Spowoduje to usunięcie każdej kolekcji, drukowanie „porzuconych” po każdej z nich, a następnie wydrukowanie „wszystkich usuniętych” po zakończeniu. Jeśli wystąpi błąd, zostanie wyświetlony stderr.


Poprzednia odpowiedź (poprzedza natywną obsługę obietnic przez Node):

Użyj obietnic Q lub obietnic Bluebird .

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

Z 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);
Nate
źródło
1
Obietnice są właściwą drogą. Bluebird to kolejna obiecująca biblioteka, która będzie dobrze działać, jeśli jest to kod krytyczny dla wydajności. Powinien to być zamiennik typu drop-in. Po prostu użyj require('bluebird').
weiyin
Dodałem przykład Bluebird. Jest trochę inaczej, ponieważ najlepszym sposobem korzystania z Bluebird jest użycie tej promisifyAllfunkcji.
Nate,
Każdy pomysł, jak działa promisifyAll. Czytałem dokumenty, ale nie rozumiem, jak obsługuje funkcje, które nie są takie jak parametry function abc(data){, ponieważ nie jest tak. function abc(err, callback){...Zasadniczo nie sądzę, aby wszystkie funkcje przyjmowały błąd jako pierwszy parametr, a wywołanie zwrotne jako drugi parametr
Muhammad Umer
@MuhammadUmer Wiele szczegółów na bluebirdjs.com/docs/api/promise.promisifyall.html
Nate
Minęło trochę czasu, odkąd sterownik MongoDB również obsługuje obietnice. Czy możesz zaktualizować swój przykład, aby to wykorzystać? .map(function(name) { return conn.collection(name).drop() })
djanowski
21

Można to zrobić, przekazując zadaniom wywołanie zwrotne, które aktualizuje udostępniony licznik. Kiedy wspólny licznik osiągnie zero, wiesz, że wszystkie zadania zostały zakończone, więc możesz kontynuować normalny przepływ.

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

Oczywiście istnieje wiele sposobów na uczynienie tego rodzaju kodu bardziej ogólnym lub wielokrotnego użytku, a każda z wielu bibliotek programowania asynchronicznego powinna mieć co najmniej jedną funkcję do tego rodzaju.

hugomg
źródło
To może nie być najłatwiejsze do wdrożenia, ale naprawdę lubię widzieć odpowiedź, która nie wymaga zewnętrznych modułów. Dziękuję Ci!
przeciwstawianie się
8

Rozwijając @freakish odpowiedź, async oferuje również każdą metodę, która wydaje się szczególnie odpowiednia dla twojego przypadku:

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, dzięki temu kod jest zarówno wydajniejszy, jak i bardziej czytelny. Pozwoliłem sobie usunąć console.log('dropped')- jeśli chcesz, użyj zamiast tego:

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');
});
Erwin Wessels
źródło
5

Robię to bez zewnętrznych bibliotek:

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');
        }
    });                
});
user435943
źródło
4

Wszystkie odpowiedzi są dość stare. Od początku 2013 roku Mongoose zaczął stopniowo obsługiwać obietnice dla wszystkich zapytań, więc myślę, że byłby to zalecany sposób tworzenia struktury kilku wywołań asynchronicznych w wymaganej kolejności.

Capaj
źródło
0

Dzięki deferred(kolejnej obietnicy / odroczonej realizacji) możesz:

// 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);
Mariusz Nowak
źródło
0

Jeśli używasz Babel lub takich transpilerów i używasz async / await, możesz zrobić:

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");
}
ganaraj
źródło
Nie możesz przekazać oddzwonienia drop()i oczekiwać zwrotu obietnicy. Czy możesz naprawić i usunąć ten przykład onDrop?
djanowski