Ограничение асинхронных вызовов в Node.js - PullRequest
44 голосов
/ 03 марта 2012

У меня есть приложение Node.js, которое получает список файлов локально и загружает их на сервер. Этот список может содержать тысячи файлов.

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

Если я выполню это с тысячами файлов, upload_file будет вызываться тысячи раз за раз и, скорее всего, умрет (или, по крайней мере, будет бороться). В синхронном мире мы бы создали пул потоков и ограничили его определенным количеством потоков. Есть ли простой способ ограничить количество асинхронных вызовов одновременно?

Ответы [ 5 ]

69 голосов
/ 03 марта 2012

Как обычно, я рекомендую асинхронный модуль Caolan McMahon .

Заставьте вашу 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
20 голосов
/ 03 марта 2012

Ответ выше, re: async на NPM - лучший ответ, но если вы хотите узнать больше о потоке управления:


Вы должны посмотреть на контрольшаблоны потока.Замечательное обсуждение шаблонов потока управления содержится в главе 7 книги узлов Mixu .А именно, я бы посмотрел на пример в 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();
7 голосов
/ 03 марта 2012

Вы должны попробовать в очереди. Я предполагаю, что обратный вызов срабатывает, когда 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 голосов
/ 13 июня 2016

Остальные ответы кажутся устаревшими. Это можно легко решить, используя paralleLimit из async . Ниже описано, как его использовать. Я не проверял это.

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

parallelLimit(tasks, 10, function(){
});
0 голосов
/ 29 мая 2019

Это может быть решено с помощью рекурсии.

Идея состоит в том, что изначально вы отправляете максимально допустимое количество запросов, и каждый из этих запросов должен рекурсивно продолжать отправлять сам себя по завершении.

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