用 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将打印后,所有集合已删除?任何第三方都可以用来简化代码。

184124 次浏览

I see you are using mongoose so you are talking about server-side JavaScript. In that case I advice looking at async module and use async.parallel(...). You will find this module really helpful - it was developed to solve the problem you are struggling with. Your code may look like this

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

The way to do it is to pass the tasks a callback that updates a shared counter. When the shared counter reaches zero you know that all tasks have finished so you can continue with your normal flow.

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

Of course, there are many ways to make this kind of code more generic or reusable and any of the many async programing libraries out there should have at least one function to do this kind of thing.

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

This drops each collection, printing “dropped” after each one, and then prints “all dropped” when complete. If an error occurs, it is displayed to stderr.


Previous answer (this pre-dates Node’s native support for Promises):

Use Q promises or Bluebird promises.

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

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

With deferred (another promise/deferred implementation) you can do:

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

Expanding upon @freakish answer, async also offers a each method, which seems especially suited for your case:

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, this makes the code both more efficient and more legible. I've taken the liberty of removing the console.log('dropped') - if you want it, use this instead:

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

All answers are quite old. Since the beginning of 2013 Mongoose started to support promises gradually for all queries, so that would be the recommended way of structuring several async calls in the required order going forward I guess.

I do this without external 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');
}
});
});

If you are using Babel or such transpilers and using async/await you could do :

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