Einfachste Möglichkeit, einige asynchrone Aufgaben in Javascript abzuwarten?

112

Ich möchte einige Mongodb-Sammlungen löschen, aber das ist eine asynchrone Aufgabe. Der Code lautet:

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

Die Konsole zeigt Folgendes an:

all dropped
dropped
dropped
dropped

Was ist der einfachste Weg, um sicherzustellen all dropped, dass gedruckt wird, nachdem alle Sammlungen gelöscht wurden? Jeder Drittanbieter kann verwendet werden, um den Code zu vereinfachen.

Freilauf
quelle

Antworten:

92

Ich sehe, mongoosedass Sie verwenden, also sprechen Sie über serverseitiges JavaScript. In diesem Fall empfehle ich, das Async-Modul zu betrachten und zu verwenden async.parallel(...). Sie werden dieses Modul sehr hilfreich finden - es wurde entwickelt, um das Problem zu lösen, mit dem Sie zu kämpfen haben. Ihr Code könnte so aussehen

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);
});
verrückt
quelle
Damit ... geschieht die forEach-Methode asynchron. Wenn die Objektliste also länger als die hier beschriebenen 3 wäre, könnte es dann nicht sein, dass bei der Auswertung von async.parallel (Aufrufe, Funktion (err, Ergebnis) Aufrufe noch nicht alle Funktionen in der ursprünglichen Liste enthalten sind?
Martin Beeby
5
@ MartinBeeby forEachist synchron. Schauen Sie hier: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Es gibt forEachunten eine Implementierung von . Nicht alles mit Rückruf ist asynchron.
freakish
2
Für die Aufzeichnung kann Async auch in einem Browser verwendet werden.
Erwin Wessels
@MartinBeeby Alles mit einem Rückruf ist asynchron, das Problem ist, dass forEach kein "Rückruf" übergeben wird, sondern nur eine reguläre Funktion (was eine falsche Verwendung der Terminologie durch Mozilla ist). In einer funktionalen Programmiersprache würden Sie eine übergebene Funktion niemals als "Rückruf" bezeichnen
3
@ ghert85 Nein, an der Terminologie ist nichts auszusetzen. Rückruf ist einfach jeder ausführbare Code, der als Argument an anderen Code übergeben wird und voraussichtlich irgendwann ausgeführt wird. Das ist die Standarddefinition. Und es kann synchron oder asynchron aufgerufen werden. Siehe dies: en.wikipedia.org/wiki/Callback_(computer_programming)
freakish
128

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

Dadurch wird jede Sammlung gelöscht, nach jeder wird "gelöscht" gedruckt, und nach Abschluss wird "alle gelöscht" gedruckt. Wenn ein Fehler auftritt, wird er angezeigt stderr.


Vorherige Antwort (dies datiert die native Unterstützung von Node für Versprechen vor):

Verwenden Sie Q- Versprechen oder Bluebird- Versprechen.

Mit F :

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

Mit 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
quelle
1
Versprechen sind der richtige Weg. Bluebird ist eine weitere Versprechungsbibliothek, die gut funktionieren würde, wenn dies in leistungskritischem Code enthalten wäre. Es sollte ein Ersatz sein. Einfach benutzen require('bluebird').
Weiyin
Ich habe ein Bluebird-Beispiel hinzugefügt. Es ist ein wenig anders, da der beste Weg, Bluebird zu verwenden, darin besteht, die promisifyAllFunktion zu verwenden.
Nate
Jede Idee, wie promisifyAll funktioniert. Ich habe Dokumente gelesen, aber ich verstehe nicht, wie es mit Funktionen umgeht, die keine Parameter mögen function abc(data){, weil es nicht so ist. function abc(err, callback){...Grundsätzlich denke ich nicht, dass alle Funktionen Fehler als ersten Parameter und Rückruf als zweiten Parameter annehmen
Muhammad Umer
@ MuhammadUmer Viele Details unter bluebirdjs.com/docs/api/promise.promisifyall.html
Nate
Es ist schon eine Weile her, dass der MongoDB-Treiber auch Versprechen unterstützt. Können Sie Ihr Beispiel aktualisieren, um dies zu nutzen? .map(function(name) { return conn.collection(name).drop() })
Djanowski
21

Der Weg, dies zu tun, besteht darin, den Aufgaben einen Rückruf zu übergeben, der einen gemeinsam genutzten Zähler aktualisiert. Wenn der gemeinsam genutzte Zähler Null erreicht, wissen Sie, dass alle Aufgaben abgeschlossen sind, sodass Sie mit Ihrem normalen Ablauf fortfahren können.

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

Natürlich gibt es viele Möglichkeiten, diese Art von Code allgemeiner oder wiederverwendbarer zu machen, und jede der vielen asynchronen Programmierbibliotheken sollte mindestens eine Funktion haben, um diese Art von Dingen auszuführen.

Hugomg
quelle
Dies ist vielleicht nicht die einfachste Implementierung, aber ich mag es wirklich, eine Antwort zu sehen, für die keine externen Module erforderlich sind. Danke dir!
counterbeing
8

Async erweitert die Antwort von @freakish und bietet außerdem jede Methode an, die für Ihren Fall besonders geeignet zu sein scheint:

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

Meiner Meinung nach macht dies den Code sowohl effizienter als auch lesbarer. Ich habe mir erlaubt, das zu entfernen console.log('dropped')- wenn Sie es wollen, verwenden Sie stattdessen Folgendes:

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
quelle
5

Ich mache das ohne externe Bibliotheken:

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
quelle
4

Alle Antworten sind ziemlich alt. Seit Anfang 2013 hat Mongoose damit begonnen, Versprechen für alle Abfragen schrittweise zu unterstützen. Dies wäre also die empfohlene Methode, um mehrere asynchrone Aufrufe in der erforderlichen Reihenfolge zu strukturieren.

Capaj
quelle
0

Mit deferred(einem weiteren Versprechen / einer verzögerten Implementierung) können Sie Folgendes tun:

// 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
quelle
0

Wenn Sie Babel oder solche Transpiler verwenden und async / await verwenden, können Sie Folgendes tun:

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
quelle
Sie können keinen Rückruf an drop()ein Versprechen weiterleiten und erwarten, dass es zurückgegeben wird. Können Sie dieses Beispiel bitte beheben und entfernen onDrop?
djanowski