用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删除所有收藏集后将被打印的最简单方法是什么?任何第三方都可以用来简化代码。

Answers:


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(calls,function(err,result)的调用时,是否还不包含原始列表中的所有函数,不是这种情况吗?
Martin Beeby 2013年

5
@MartinBeeby forEach是同步的。在这里查看:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference / ...forEach底部的实现。并非所有带回调的都是异步的。
古怪的

2
作为记录,异步也可以在浏览器中使用。
Erwin Wessels 2014年

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

蓝鸟

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是另一个Promise库,如果在性能关键的代码中,它将很好地工作。它应该是直接替换。只需使用require('bluebird')
威音2014年

我添加了一个蓝鸟示例。有所不同,因为使用Bluebird的最佳方法是使用该promisifyAll功能。
2014年

任何想法promisifyAll是如何工作的..我读过文档,但我不明白的是它如何处理不带参数的函数,如function abc(data){,因为它不是这样,function abc(err, callback){...基本上我不认为所有函数都将错误作为第一参数并将回调作为第二参数
穆罕默德·乌默


自MongoDB驱动程序也支持promises以来已有一段时间了。您可以更新您的示例以利用此优势吗?.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);

当然,有很多方法可以使这种代码更通用或更可重用,并且那里的许多异步编程库中的任何一个都应该至少具有一个函数来执行这种操作。


这可能不是最容易实现的,但是我真的很喜欢看到不需要外部模块的答案。谢谢!
2013年

8

扩展@freakish答案,异步还提供了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年初开始猫鼬支持承诺逐步所有查询,所以这将是在所要求的顺序结构几个异步调用前进我想推荐的方式。


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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.