如何在没有嵌套的情况下对异步函数组进行排序(jquery $ .when .then)

时间:2015-05-29 23:57:46

标签: javascript jquery asynchronous promise

我有一组异步函数,其中每个组都依赖于前一组的结果,但在一个组中,函数可以按任何顺序执行/解析。

如何在没有每个组的嵌套级别的情况下按顺序调用函数组?

    function waste(secs)
    {
    	var prom=$.Deferred();
    	setTimeout(function(){
            $('body').append('<p>'+secs+'</p>');
            prom.resolve();
        }, secs*1000);
    	return prom;
    }
    
    $.when(
    	waste(5),
    	waste(4)
    ).then(function(){
    	$.when(
    		waste(3),
    		waste(2)
    	).then(function(){
    		$.when(
    			waste(1),
    			waste(0)
    		).then(function(){
    			$('body').append('<p>done</p>');
    		});
    	});
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

以下是我到目前为止所发现的内容,但每个部分都有一种尴尬的return $.when(缩进:

function waste(secs)
{
    var prom=$.Deferred();
    setTimeout(function(){
        $('body').append('<p>'+secs+'</p>');
        prom.resolve();
    }, secs*1000);
    return prom;
}
    
$.when(
    waste(5),
    waste(4)
).then(function(){
    return $.when(
        waste(3),
        waste(2)
    );
}).then(function(){
    return $.when(
        waste(1),
        waste(0)
    );
}).then(function(){
    $('body').append('<p>done</p>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

在执行下一个组之前,组中所有方法需要完成的异步函数组的顺序排序是什么样的最佳实践方法?

我觉得理想情况下,这样的事情就是我希望的工作:

$.when(
    waste(5),
    waste(4)
).when(
    waste(3),
    waste(2)
).when(
    waste(1),
    waste(0)
).then(function(){
    $('body').append('<p>done</p>');
});

3 个答案:

答案 0 :(得分:3)

如何从不同的角度解决问题:您想要将哪个输入映射到哪个输出?让我们看看我们是否可以为此充分概括您的问题。

基本上你有一系列并行操作。

每个操作都可以由函数和参数列表表示,等待在适当的时间到来时执行。让我们将该抽象命名为 callDef

[func, arg1, arg2, arg3]

或者,为了您的例子:

[waste, 5]

运行它的实用程序很简单:

function call(callDef) {
    return callDef[0].apply(null, callDef.slice(1));
}

现在每组操作都可以表示为callDef 本身,假设我们有一个callInParallel函数,它带有 callDefs 的列表:

[callInParallel, [callDef, callDef]]

[ callInParallel, [[waste, 5], [waste, 4]] ],

callInParallel功能同样简单:

function callInParallel(callDefs) {
    return $.when.apply($, callDefs.map(call));
}

因此,您的整个输入可以表示为嵌套 callDefs 的列表:

[
    [ callInParallel, [[waste, 5], [waste, 4]] ],
    [ callInParallel, [[waste, 3], [waste, 2]] ],
    [ callInParallel, [[waste, 1], [waste, 0]] ]
]

现在我们可以通过call单独执行leaf callDefs ,并通过callInParallel并行执行每组 callDefs

缺少的位是一个callInSequence函数,它在前一个 callDef 完成后执行。

由于callInParallel返回一个promise,我们可以将它作为执行下一个 callDef 的先决条件:我们需要将前一个操作的结果存储在一个{{1}中变量并通过wait将下一个操作链接到它。之后,我们可以使用当前操作覆盖.then(),有效地为下一步做好准备。

当所有步骤都被链接时,我们可以再次通过wait返回一个总体承诺,整体承诺在最后一个之前没有解决,而在倒数第二个之前没有解决,依此类推:

$.when

由于我们的抽象允许,我们可以在一个 callDef 中表示整个操作:

function callInSequence(callDefs) {
    var wait = $.Deferred().resolve();
    return $.when.apply($, callDefs.map(function (callDef) {
        return wait = wait.then(function () {
            return call(callDef);
        });
    }));
}

全部放在一起:

[
    callInSequence, [
        [ callInParallel, [[waste, 5], [waste, 4]] ],
        [ callInParallel, [[waste, 3], [waste, 2]] ],
        [ callInParallel, [[waste, 1], [waste, 0]] ]
    ]
]
function waste(secs) {
    var result = $.Deferred();
    setTimeout(function () {
        $('body').append('<div>'+secs+'</div>');
        result.resolve('done ' + secs);
    }, secs * 1000);
    return result.promise();
}
function call(callDef) {
    return callDef[0].apply(null, callDef.slice(1));
}
function callInParallel(callDefs) {
    return $.when.apply($, callDefs.map(call));
}
function callInSequence(callDefs) {
    var wait = $.Deferred().resolve();
    return $.when.apply($, callDefs.map(function (callDef) {
        return wait = wait.then(function () {
            return call(callDef);
        });
    }));
}
// ---------------------------------------------------------------------------

var everything = [
    callInSequence, [
        [ callInParallel, [[waste, 5], [waste, 4]] ],
        [ callInParallel, [[waste, 3], [waste, 2]] ],
        [ callInParallel, [[waste, 1], [waste, 0]] ]
    ]
];

call(everything).done(function () {
    $('<hr>').appendTo('body');
    $('<pre>', {text: JSON.stringify(arguments, null, 2)}).appendTo('body');
}).fail(function (error) {
    console.log(error);
});
$('<div>sequence has started...</div>').appendTo('body');

注意:

  • 此代码需要jQuery 1.8+,因为Deferreds在1.8中的行为方式发生了变化。
  • 您可以采用不同的方式组合:

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
  • 上面没有对var everything = [ callInSequence, [ [ callInSequence, [[waste, 5], [waste, 4]] ], [ callInParallel, [[waste, 3], [waste, 2]] ], [ callInSequence, [[waste, 1], [waste, 0]] ] ] ]; 的处理,将针对全局对象(thisArg)调用每个函数。我把实施这个作为练习。有两种方法可以做到这一点,一种是使用window,另一种是扩展.bind()的定义。

答案 1 :(得分:0)

  

如果请求失败,则不确定它是如何工作的,

function waste(secs) {
    var prom = $.Deferred();
    setTimeout(function() {
      $('body').append('<p>' + secs + '</p>');
      prom.resolve();
    }, secs * 1000);
    return prom.promise();
  }
  // define error handler
var err = function err(e) {
  console.log(e)
};

$.when(
waste(5),
waste(4)
).then(function() {
  return $.when(
    waste(3),
    waste(2)
  ).fail(err);
}, err).then(function() {
  return $.when(
    waste(1),
    waste(0)
  ).fail(err);
}, err).then(function() {
  $('body').append('<p>done</p>');
}, err);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

答案 2 :(得分:0)

我假设$.when相当于Promise.all。在那种情况下,你正在做的是正确的。我认为你所做的一切都是为了改善审美观,加入你的一些方面:

$.when(waste(5),waste(4))
  .then(function() { return $.when(waste(3), waste(2)); })
  .then(function() { return $.when(waste(1), waste(0)); })
  .then(function(){
    $('body').append('<p>done</p>');
  }, errorHandler);

你有没有理由为此使用jQuery?这可以通过Promises本地完成:

function append(secs) {
  return Promise(function(resolve, reject) {
    setTimeout(function(){
      $('body').append('<p>'+secs+'</p>');
      resolve();
    }, !isNaN(secs) && secs*1000);
  });
}

Promise.all([append(5), append(4)])
  .then(function() { return Promise.all([append(3), append(2)]); })
  .then(function() { return Promise.all([append(1), append(0)]); })
  .then(function() { return append('done'); }, errorHandler);