限制 Node.js 中的异步调用

Posted

技术标签:

【中文标题】限制 Node.js 中的异步调用【英文标题】:Limiting asynchronous calls in Node.js 【发布时间】:2012-03-21 08:17:06 【问题描述】:

我有一个 Node.js 应用程序,它在本地获取文件列表并将它们上传到服务器。此列表可能包含数千个文件。

for (var i = 0; i < files.length; i++) 
   upload_file(files[i]);

如果我对数千个文件执行此操作,upload_file 将同时被调用数千次,并且很可能会死掉(或者至少是挣扎)。在同步世界中,我们将创建一个线程池并将其限制为一定数量的线程。有没有一种简单的方法来限制一次执行多少个异步调用?

【问题讨论】:

一个类似但速率限制(每秒/分钟)在这里:***.com/questions/20253425/… 这甚至是一个问题的事实是我不喜欢 Node.js 的原因 【参考方案1】:

您应该尝试排队。我假设upload_file() 完成时会触发回调。像这样的东西应该可以解决问题(未经测试):

function upload_files(files, maxSimultaneousUploads, callback) 
    var runningUploads = 0,
        startedUploads = 0,
        finishedUploads = 0;

    function next() 
        runningUploads--;
        finishedUploads++;

        if (finishedUploads == files.length) 
            callback();
         else 
            // Make sure that we are running at the maximum capacity.
            queue();
        
    

    function queue() 
        // Run as many uploads as possible while not exceeding the given limit.
        while (startedUploads < files.length && runningUploads < maxSimultaneousUploads) 
            runningUploads++;
            upload_file(files[startedUploads++], next);
        
    

    // Start the upload!
    queue();

【讨论】:

【参考方案2】:

上面的答案,回复:NPM 上的async 是最好的答案,但如果您想了解更多关于控制流的信息:


您应该研究控制流模式。 Chapter 7 of Mixu's Node Book 中有关于控制流模式的精彩讨论。也就是说,我会看 7.2.3 中的示例:有限并行 - 一个异步、并行、并发受限的循环

我改编了他的例子:

function doUpload() 
    // perform file read & upload here...


var files   = [...];
var limit   = 10;       // concurrent read / upload limit
var running = 0;        // number of running async file operations

function uploader() 
    while(running < limit && files.length > 0) 
        var file = files.shift();
        doUpload(file, function() 
            running--;
            if(files.length > 0)
                uploader();
        );
        running++;
    


uploader();

【讨论】:

它工作正常并且只运行指定数量的异步操作;但是我注意到doUpload 内的file 的值(例如running--; 之前)不包含预期值,例如console.log(file) 将在前10 行打印同一个文件10 次(如果@987654328 @ 的值为 10,即)【参考方案3】:

像往常一样,我推荐 Caolan McMahon 的 async module。

让您的upload_file 函数将回调作为第二个参数:

var async = require("async");

function upload_file(file, callback) 
    // Do funky stuff with file
    callback();


var queue = async.queue(upload_file, 10); // Run ten simultaneous uploads

queue.drain = function() 
    console.log("All files are uploaded");
;

// Queue your files for upload
queue.push(files);

queue.concurrency = 20; // Increase to twenty simultaneous uploads

【讨论】:

我的函数永远不会返回。我收到“所有文件已上传”消息,但我的进程并未终止。【参考方案4】:

其他答案似乎已过时。这可以使用async 中的paralleLimit 轻松解决。下面是如何使用它。我没有测试过。

var tasks = files.map(function(f) 
    return function(callback) 
        upload_file(f, callback)
    
);

parallelLimit(tasks, 10, function()
);

【讨论】:

队列函数和parallellimit有区别吗?【参考方案5】:

可以用递归来解决。

这个想法是,最初您发送最大允许数量的请求,并且这些请求中的每一个都应该在完成时递归地继续发送自己。

function batchUpload(files, concurrentRequestsLimit) 
    return new Promise(resolve => 
        var responses = [];
        var index = 0;

        function recursiveUpload() 
            if (index === files.length) 
                return;
            
            upload_file(files[index++]).then(r => 
                responses.push(r);
                if (responses.length === files.length) 
                    resolve(responses);
                 else 
                    recursiveUpload();
                
            );
        

        for (var i = 0; i < concurrentRequestsLimit; i++) 
            recursiveUpload();
        
    );


var files = [
    'file_1',
    'file_2',
    'file_3',
    ...
    'file_100'
];
batchUpload(files, 5).then(responses => 
   console.log(responses);
);

【讨论】:

以上是关于限制 Node.js 中的异步调用的主要内容,如果未能解决你的问题,请参考以下文章

在 node.js 中调用异步函数

Node.js 中对 API 的异步调用模式

Node.js/ 使 redis 调用异步

带有 Express 异步 API 调用的 Node.JS

Node.js - 如何使用回调调用异步函数?

Node.js中的异步I/O是如何进行的?