【问题标题】:Simplest way to wait some asynchronous tasks complete, in Javascript?在Javascript中等待一些异步任务完成的最简单方法?
【发布时间】:2012-05-20 01:24:13
【问题描述】:

我想删除一些 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 的最简单方法是什么?任何 3rd-party 都可以用来简化代码。

【问题讨论】:

    标签: javascript node.js asynchronous mongoose synchronous


    【解决方案1】:

    如果你使用 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吗?
    【解决方案2】:

    我看到您使用的是mongoose,所以您在谈论服务器端 JavaScript。在这种情况下,我建议查看async module 并使用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) 时调用还没有包含原始列表中的所有函数吗?
    • @MartinBeeby forEach 是同步的。看看这里:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 底部有forEach 的实现。并非所有带有回调的东西都是异步的。
    • 为了记录,async也可以在浏览器中使用。
    • @MartinBeeby 带有回调的一切都是异步的,问题是 forEach 没有被传递一个“回调”,而只是一个常规函数(Mozilla 对术语的错误使用)。在函数式编程语言中,您永远不会将传递的函数称为“回调”
    • @ghert85 不,术语没有错。回调只是作为参数传递给其他代码并预计在某个时候执行的任何可执行代码。这就是标准定义。并且可以同步或异步调用。看到这个:en.wikipedia.org/wiki/Callback_(computer_programming)
    【解决方案3】:

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

    这会丢弃每个集合,在每个集合之后打印“dropped”,然后在完成时打印“all dropped”。如果发生错误,则显示到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);
    

    【讨论】:

    • 承诺是必经之路。 Bluebird 是另一个 Promise 库,如果它在性能关键代码中运行良好。它应该是一个替代品。只需使用require('bluebird')
    • 我添加了一个 Bluebird 示例。这有点不同,因为使用 Bluebird 的最佳方式是使用 promisifyAll 功能。
    • 知道 promisifyAll 是如何工作的。我读过文档,但我不明白它是如何处理没有参数的函数的,比如function abc(data){,因为它不像function abc(err, callback){... 基本上我不要认为所有函数都将错误作为第一个参数,将回调作为第二个参数
    • MongoDB 驱动程序也支持 Promise 已经有一段时间了。您可以更新您的示例以利用这一点吗? .map(function(name) { return conn.collection(name).drop() })
    【解决方案4】:

    我在没有外部库的情况下这样做:

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

    【讨论】:

      【解决方案5】:

      所有答案都很老了。从 2013 年初开始,Mongoose 开始逐渐支持 promises 用于所有查询,因此我猜这将是按照所需顺序构建多个异步调用的推荐方式。

      【讨论】:

        【解决方案6】:

        扩展@freakish 的答案,async 还提供了 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');
        });
        

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            这样做的方法是向任务传递一个更新共享计数器的回调。当共享计数器达到零时,您就知道所有任务都已完成,因此您可以继续正常流程。

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

            当然,有很多方法可以使这种代码更通用或可重用,任何many async programing libraries 都应该至少有一个函数来做这种事情。

            【讨论】:

            • 这可能不是最容易实现的,但我真的很喜欢看到不需要外部模块的答案。谢谢!
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多