Каков наилучший способ перебора или рекурсии огромного количества огромных функций без превышения предела стека? - PullRequest
7 голосов
/ 01 февраля 2012

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

Ошибка говорит: RangeError: Превышен максимальный размер стека вызовов

Мне любопытно, возникла ли у кого-нибудь еще такая ситуация и узнать, как они справились с этим. Я уже начал разбивать свой код на пару дополнительных рабочих файлов, но даже в этом случае каждый раз, когда я обрабатываю узел данных, ему нужно коснуться 2 баз данных (не более 25 вызовов для обновления различных таблиц) и выполнить ряд проверок санации. .

Я полностью готов признать, что, возможно, я делаю что-то неоптимальное, если это так, но был бы признателен за некоторые рекомендации, если есть более оптимальный способ.

Вот пример кода, который я запускаю на данных:

app.post('/initspeaker', function(req, res) {
    // if the Admin ID is not present ignore
    if(req.body.xyzid!=config.adminid) {
        res.send( {} );
        return;
    }

    var gcnt = 0, dbsize = 0, goutput = [], goutputdata = [], xyzuserdataCallers = [];

    xyz.loadbatchfile( xyz.getbatchurl("speakers", "csv"), function(data) {
        var parsed = csv.parse(data);
        console.log("lexicon", parsed[0]);

        for(var i=1;i<parsed.length;i++) {
            if(typeof parsed[i][0] != 'undefined' && parsed[i][0]!='name') {
                var xyzevent = require('./lib/model/xyz_speaker').create(parsed[i], parsed[0]);
                xyzevent.isPresenter = true;
                goutput.push(xyzevent);
            }
        }
        dbsize = goutput.length;

        xyzuserdataCallers = [new xyzuserdata(),
                                    new xyzuserdata(),
                                    new xyzuserdata(),
                                    new xyzuserdata(),
                                    new xyzuserdata(),
                                    new xyzuserdata(),
                                    new xyzuserdata(),
                                    new xyzuserdata()
                                ];
        // insert all Scheduled Items into the DB                   
        xyzuserdataCallers[0].sendSpeakerData(goutput[0]);
        for(var i=1;i<xyzuserdataCallers;i++) {
            xyzuserdataCallers[i].sendSpeakerData(8008);
        }

        //sendSpeakerData(goutput[0]);
    });

    var callback = function(data, func) {
        //console.log(data);
        if(data && data!=8008) {
            if(gcnt>=dbsize) {
                res.send("done");
            } else {
                gcnt++;
                func.sendSpeakerData(goutput[gcnt]);
            }
        } else {
            gcnt++;
            func.sendSpeakerData(goutput[gcnt]);
        }
    };

    // callback loop for fetching registrants for events from SMW
    var xyzuserdata = function() {};
    xyzuserdata.prototype.sendSpeakerData = function(data) {
        var thisfunc = this;

        if(data && data!=8008) {
            //console.log('creating user from data', gcnt, dbsize);
            var userdata = require('./lib/model/user').create(data.toObject());
            var speakerdata = userdata.toObject();
            speakerdata.uid = uuid.v1();
            speakerdata.isPresenter = true;

            couchdb.insert(speakerdata, config.couch.db.user, function($data) {
                if($data==false) {
                    // if this fails it is probably due to a UID colliding
                    console.log("*** trying user data again ***");
                    speakerdata.uid = uuid.v1();
                    arguments.callee( speakerdata );
                } else {
                    callback($data, thisfunc);
                }
            });
        } else {
            gcnt++;
            arguments.callee(goutput[gcnt]);
        }
    };

});

Здесь определены пара классов и предметов, которые требуют некоторого введения:

  • Я использую Express.js + хостинг CouchDB, и это отвечает на запрос POST
  • Существует класс синтаксического анализатора CSV, который загружает список событий, которые управляют извлечением данных о динамике
  • Каждое событие может иметь n пользователей (в настоящее время около 8K пользователей для всех событий)
  • Я использую шаблон, который загружает все данные / пользователей, прежде чем пытаться проанализировать какие-либо из них
  • Каждый загруженный пользователь (внешний источник данных) преобразуется в объект, который я могу использовать, а также очищается (полоски и т. Д.)
  • Каждый пользователь затем вставляется в CouchDB

Этот код работает в приложении, но через некоторое время я получаю сообщение о том, что было сделано более 11 800 звонков и приложение перестало работать. Это не ошибка, которая содержит трассировку стека, как если бы это была ошибка кода, она завершается из-за количества выполняемых вызовов.

Опять же, любая помощь / комментарий / направление будет приветствоваться.

Ответы [ 2 ]

5 голосов
/ 02 февраля 2012

Похоже, xyzuserdata.sendSpeakerData и обратный вызов используются рекурсивно, чтобы поддерживать последовательные вызовы БД.В какой-то момент у вас заканчивается стек вызовов ...

Существует несколько модулей, облегчающих последовательное выполнение, например Step или Flow-JS .

Flow-JS даже имеет вспомогательную функцию для последовательного применения функции к элементам массива:

flow.serialForEach(goutput, xyzuserdata.sendSpeakerData, ...)

Я написал небольшую тестовую программу с использованием flow.serialForEach, но, к сожалению, смог получить Maximum call stack size exceeded ошибка - похоже, что Flow-JS использует стек вызовов аналогичным образом для синхронизации данных.

Другой подход, который не создает стек вызовов, - это избежать рекурсии и использовать setTimeout сзначение времени ожидания 0, чтобы запланировать обратный вызов.См. http://metaduck.com/post/2675027550/asynchronous-iteration-patterns-in-node-js

Вы можете попробовать заменить обратный вызов на

setTimeout(callback, 0, [$data, thisfunc])
1 голос
/ 06 февраля 2013

Рекурсия очень полезна для синхронизации асинхронных операций - поэтому она используется в flow.js и т. Д.

Однако, если вы хотите обрабатывать неограниченное количество элементов в массиве или буферизованном потоке, выпотребуется использовать источник событий node.js.

в псевдо-ish-код :

 ee = eventemitter
 arr = A_very_long_array_to_process
 callback = callback_to_call_once_either_with_an_error_or_when_done

 // the worker function does everything
 processOne() {
   var 
      next = arr. shift();
   if( !arr )
      ee.emit ( 'finished' )
      return

   process( function( err, response) {
      if( err )
         callback( err, response )
      else
         ee.emit( 'done-one' )
    } );
 }    

 // here we process the final event that the worker will throw when done
 ee.on( 'finished', function() { callback( null, 'we processed the entire array!'); } );

 // here we say what to do after one thing has been processed
 ee.on( 'done-one', function() { processOne(); } );

 // here we get the ball rolling
 processOne();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...