在不超出堆栈限制的情况下迭代或递归大量函数的最佳方法是什么?

时间:2012-02-01 17:27:28

标签: javascript node.js couchdb express

我有一个应用程序,我在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 个答案:

答案 0 :(得分:5)

它看起来像xyzuserdata.sendSpeakerData&amp;正在递归使用回调以保持DB调用顺序。在某些时候你用完了堆栈......

有几个模块可以简化串行执行,例如StepFlow-JS

Flow-JS甚至还有一个便利功能,可以在数组元素上串行应用函数:

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

我使用flow.serialForEach编写了一个小测试程序,但遗憾的是能够得到Maximum call stack size exceeded错误 - 看起来Flow-JS以类似的方式使用调用堆栈来保持同步。 / p>

另一种不构建调用堆栈的方法是避免递归并使用超时值为0的setTimeout来调度回调调用。看到 http://metaduck.com/post/2675027550/asynchronous-iteration-patterns-in-node-js

您可以尝试使用

替换回拨电话
setTimeout(callback, 0, [$data, thisfunc])

答案 1 :(得分:1)

递归对于同步异步操作非常有用 - 这就是它在flow.js等中使用的原因。

但是,如果要在数组或缓冲流中处理无限数量的元素,则需要使用node.js的事件发射器。

伪代码

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