Как обработать большой массив, применяя асинхронную функцию для каждого элемента в nodejs?

Я работаю с zombie.js, чтобы очистить один сайт, я должен использовать стиль обратного вызова для подключения к каждому URL. Дело в том, что у меня есть массив URL-адресов, и мне нужно обрабатывать каждый URL-адрес с помощью асинхронной функции. Это мой первый подход:

Array urls = {http..., http...};
function process_url(index)
{
   if(index == urls.length)
      return;

   async_function(url, 
                  function() { 
                        ... 
                        //parse the url 
                        ...
                        // Process the next url
                        process_url(index++);
                       }
                   );
}

process_url(0)

Без использования какой-либо сторонней библиотеки nodejs для использования асинхронной функции в качестве функции синхронизации или ожидания функции (wait.for, synchornized, mocha), я думаю, что для решения этой проблемы я не знаю, что произойдет, если массив слишком большой. Функция высвобождается из памяти при вызове следующей функции? или все функции находятся в памяти до конца?

Есть идеи?

1 ответ

Решение

Ваша схема будет работать. Я называю это "ручная последовательность асинхронных операций".

Универсальная версия того, что вы делаете, будет выглядеть так:

function processItem(data, callback) {
    // do your async function here
    // for example, let's suppose it was an http request using the request module
    request(data, callback);
}

function processArray(array, fn) {
    var index = 0;

    function next() {
        if (index < array.length) {
            fn(array[index++], function(err, result) {
                // process error here
                if (err) return;
                // process result here
                next();
            });
        }
    }
    next();
}

processArray(arr, processItem);

Что касается ваших конкретных вопросов:

Я не знаю, что произойдет, если массив слишком большой. Функция высвобождается из памяти при вызове следующей функции? или все функции находятся в памяти до конца?

Память в Javascript освобождается, когда на нее больше не ссылается ни один работающий код, и когда сборщик мусора получает время для запуска. Так как вы выполняете здесь ряд асинхронных операций, вполне вероятно, что сборщик мусора получит возможность регулярно запускаться в ожидании ответа http от асинхронной операции, чтобы очистить память тогда. Функции - это просто еще один тип объектов в Javascript, и они собирают мусор, как и все остальное. Когда они больше не являются ссылками при запуске кода, они имеют право на сборку мусора.

В вашем конкретном коде, потому что вы перезвоните process_url() только в асинхронном обратном вызове нет наращивания стека (как в обычной рекурсии). Предыдущий экземпляр process_url() уже завершена ДО того, как будет вызван асинхронный обратный вызов, и ДО того, как вы вызовете следующую итерацию process_url(),


В целом, управление и координация нескольких асинхронных операций намного проще с использованием обещаний, которые встроены в текущие версии node.js и являются частью стандарта ES6 ECMAScript. Для использования обещаний в текущих версиях node.js. не требуется никаких внешних библиотек.

Список нескольких различных способов упорядочения асинхронных операций в массиве, как с использованием обещаний, так и без использования обещаний, см. В следующих источниках:

Как синхронизировать последовательность обещаний?,

Первым шагом в использовании обещаний является "обещание" вашей асинхронной функции, чтобы она возвращала обещание, а не выполняла обратный вызов.

function async_function_promise(url) {
    return new Promise(function(resolve, reject) {
        async_function(url, function(err, result) {
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
}

Теперь у вас есть версия вашей функции, которая возвращает обещания.

Если вы хотите, чтобы ваши асинхронные операции выполнялись по одной за раз, чтобы следующая не начиналась до тех пор, пока не завершится предыдущая, то для этого следует использовать обычный шаблон проектирования. .reduce() как это:

function process_urls(array) {
    return array.reduce(function(p, url) {
        return p.then(function(priorResult) {
            return async_function_promise(url);
        });
    }, Promise.resolve());
}

Затем вы можете назвать это так:

var myArray = ["url1", "url2", ...];
process_urls(myArray).then(function(finalResult) {
    // all of them are done here
}, function(err) {
    // error here
});

Существуют также библиотеки Promise, которые имеют некоторые полезные функции, упрощающие этот тип кодирования. Я сам пользуюсь библиотекой обещаний Bluebird. Вот как ваш код будет выглядеть с использованием Bluebird:

var Promise = require('bluebird');
var async_function_promise = Promise.promisify(async_function);

function process_urls(array) {
    return Promise.map(array, async_function_promise, {concurrency: 1});
}

process_urls(myArray).then(function(allResults) {
    // all of them are done here and allResults is an array of the results
}, function(err) {
    // error here
});

Обратите внимание, вы можете изменить concurrency ценить то, что вы хотите здесь. Например, вы, вероятно, получите быструю сквозную производительность, если увеличите ее до 2 а также 5 (зависит от реализации сервера от того, как это лучше всего оптимизировать).

Другие вопросы по тегам