Javascript - 执行长链承诺时的过多递归

时间:2013-11-28 13:21:59

标签: javascript algorithm recursion promise jquery-deferred

我有一系列顺序执行的异步函数。所以我使用promises来创建promise队列。不幸的是,在Linux上的Firefox 25.0.1上,我遇到了too much recursion JavaScript错误。它在Mac上的相同Firefox版本上运行良好,它也适用于chrome。但我需要它在任何地方工作,包括Linux。您能否建议更好地实施executePromiseQueueSync功能?

//-----------------------------------------------------------------------------------------

function executePromiseQueueSync(queue){
     var seed = $.Deferred(); 
     var acc = seed;

     for (var i = 0; i < queue.length; ++i )
     {
         var promise = queue[i];
         acc = acc.then(promise.funct.apply(null, promise.argmnt));
     }

    seed.resolve();
    return acc;
}

//-----------------------------------------------------------------------------------------

function someTask(){
    var dfd = new jQuery.Deferred();
    dfd.notify();
    dfd.resolve();
    return dfd.promise();
}

//-----------------------------------------------------------------------------------------

$(function(){
    var promisesQueue = []

    for(var i = 0; i < 200; ++i){
        promisesQueue.push({funct:someTask, argmnt:[]});
    }

    executePromiseQueueSync(promisesQueue).then(function(){alert('done!');});
});

//-----------------------------------------------------------------------------------------

JSFiddle示例:http://jsfiddle.net/C2YN4/4/

到目前为止我有什么(我对其他命题和更正非常开放):

function executePromiseQueueSync(queue){

    if (queue.length > TRESHOLD){
        var i,j;
        var shortQueue = []
        for (i=0,j=queue.length; i<j; i+=TRESHOLD) {
          temparray = queue.slice(i, i+TRESHOLD);
          shortQueue.push({funct:executePromiseQueueSync, argmnt:[temparray]});
        }
        return executePromiseQueueSync(shortQueue);
    }

    var seed = $.Deferred(); 
    var acc = seed;

    for (var i = 0; i < queue.length; ++i )
    {
         var promise = queue[i];
         acc = acc.then(promise.funct.apply(null, promise.argmnt));
    }

    seed.resolve();
    return acc;
}

所以基本的想法是建立一个承诺树而不是承诺链。这样我们就不会深入到堆栈中。

示例:http://jsfiddle.net/fMBJK/1/

2 个答案:

答案 0 :(得分:2)

你可以使用jQuery.when〜虽然在200多个论点中传递可能有点疯狂:

http://jsfiddle.net/6j6em/1/

来自小提琴的代码

使用jQuery的JavaScript

var resolves = [];

function log(msg){
  $('#log').append('<div><small>' + msg + '</small></div>');
}

function someTask(i){
  var dfd = new $.Deferred();
  log('created task '+i);
  resolves.push(function(){
    log('resolved task '+i);
    dfd.resolve();
  });
  return dfd.promise();
}

$(function(){

  $('#resolve1').click(function(){
    for ( var i=0; i<resolves.length; i++ ) {
      resolves[i]();
    }
  });

  $('#resolve2').click(function(){
    for ( var i=0; i<resolves.length; i++ ) {
      if ( i == 5 ) continue;
      resolves[i]();
    }
  });

  $('#resolve3').click(function(){
    resolves[5]();
  });

  var i, queue = []; for(i=0; i<200; ++i){ queue.push(someTask(i)); }

  (jQuery.when.apply(jQuery, queue))
    .done(function(){
      log('all resolved!');
      alert('all resolved!');
    })
  ;

});

此示例的标记

<button id="resolve1">Resolve all</button>
&nbsp;&nbsp;
<button id="resolve2">Resolve (all but one)</button>
<button id="resolve3">Resolve (the remaining one)</button>
<div id="log"></div>

此示例的CSS

#log {
    border: 1px solid black; 
    padding: 10px; 
    width: 400px; 
    height: 200px; 
    overflow: auto;
}


解释

以上代码大部分只是为了说明,关键点是:

  var i, queue = []; for(i=0; i<200; ++i){ queue.push(someTask(i)); }

  (jQuery.when.apply(jQuery, queue))
    .done(function(){
      log('all resolved!');
      alert('all resolved!');
    })
  ;

上面生成一个promise对象数组,然后使用apply将它们传递给jQuery.whendone然后处理创建正确的结构,以便在它们全部完成后触发function log(msg){ $('#log').append('<div><small>' + msg + '</small></div>'); } function someTask(i){ var dfd = new $.Deferred(); log(':: created task '+i); setTimeout(function(){ log(':: resolved task '+i); dfd.resolve(); },50); return dfd.promise(); } $(function(){ var Queue; Queue = function( items ){ this.items = items.slice(0); this.promise = $.Deferred(); }; Queue.prototype.next = function(){ log(':: next task triggered'); var q = this; q.lastItem = q.items.shift(); if ( q.lastItem ) { q.lastPromise = q.lastItem.func.apply( null, q.lastItem.args ); q.lastPromise.then(function(){ /// include a setTimeout 0 to avoid possible stack/recursion errors. setTimeout(function(){ q.next(); },0); }); } else { /// we are finished q.promise.resolve(); } }; Queue.prototype.run = function(){ this.next(); }; var i, items = []; for(i=0; i<200; ++i){ items.push({ func: someTask, args:[i] }); } var q = new Queue( items ); q.promise.done(function(){ log(':: done!'); alert('Done!'); }); q.run(); }); 回调。那就是你想要这种行为。如果你想要一个等待每个promise对象按顺序解析的系统,在触发下一个任务之前,你需要一些不同的东西。


更新

就按顺序发射任务而言,你需要一种不同的方法,如下所示:

http://jsfiddle.net/6j6em/2/

Queue

这构建了一个定制的$.Deferred()对象,用于跟踪承诺列表,在第一个成功后,触发下一个。但是,这段代码显然需要进行错误处理。


更新x2

您不能依赖每个承诺的进度,因为任何时候只有一个被触发。您可以将自己的通知调用添加到整个var Queue; Queue = function( items ){ this.items = items.slice(0); this.promise = $.Deferred(); this.count = 0; }; Queue.prototype.next = function(){ log(':: next task triggered'); var q = this; q.lastItem = q.items.shift(); if ( q.lastItem ) { q.lastPromise = q.lastItem.func.apply( null, q.lastItem.args ); q.lastPromise.then(function(){ q.promise.notify(q.count++); q.next(); }); } else { q.promise.resolve(); } }; Queue.prototype.run = function(){ this.next(); }; var q = new Queue( items ); q.promise .done(function(){log(':: done!');}) .progress(function(p){log('::progress ' + p);}) ; q.run(); 对象中。

{{1}}

答案 1 :(得分:1)

FWIW ......我有点晚了,但这是我对你的队列要求的实现:

function RunnablePromise(func, args, thisArg) {
    var def = $.Deferred(), prm = def.promise();

    prm.run = function () {
        func.apply(thisArg, args).done(def.resolve).fail(def.reject);
        return prm;
    };
    return prm;
}

function PromiseQueue() {
    var q = [], overall = $.Deferred(), self = this;

    this.run = function () {
        var runnable = q.shift();
        if (runnable) {
            overall.notify(q.length);
            runnable.run().done(self.run).fail(overall.reject);
        } else {
            overall.resolve();
        }
        return overall.promise();
    };
    this.append = function (task, args, thisArg) {
        var runnable = new RunnablePromise(task, args, thisArg);
        q.push(runnable);
        return runnable;
    };
}

像这样使用:

var pq = new PromiseQueue(), i,
    success = function (i) { console.log("task done: " + i); };

for (i = 0; i < 200; i++) {
    // .append() returns the individual task's promise
    pq.append(someAsyncTask, [i]).done(success);
}

// .run() returns the entire queue's promise
pq.run().progress(function (remain) {
    console.log("remaining: " + remain);
}).done(function () {
    console.log("all done!");
}).fail(function () {
    console.log("error!");
});

http://jsfiddle.net/CxNDv/