如何在不嵌套的情况下对异步函数组进行排序.当区间)

How to sequence groups of async functions without nesting (jquery $.when .then)

本文关键字:数组 排序 区间 函数 异步 嵌套 情况下      更新时间:2023-09-26

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

如何按顺序调用函数组而不为每个组设置嵌套级别?

    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>');
});

从另一个角度解决这个问题:您希望将哪些输入映射到哪些输出?让我们看看我们是否能把你的问题推广到这一点。


基本上你有一个并行操作集合的序列。

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

[func, arg1, arg2, arg3]

或者,对于你的例子来说:

[waste, 5]

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

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

现在,每组操作都可以表示为callDef 本身,假设我们有一个callInParallel函数,该函数接受callDef : 的列表。
[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单独执行叶子callDefs,并通过callInParallel并行执行它们的每组。

缺失的位是callInSequence函数,在前一个callDef完成后执行每个callDef

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

当所有步骤被链接时,我们可以返回一个总承诺,同样通过$.when,总承诺在最后一个之前不解决,在倒数第二个之前不解决,以此类推:

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

由于抽象允许,我们可以用一个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');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

指出:

  • 这个代码需要jQuery 1.8+,因为延迟的行为在1.8中发生了变化。
  • 你可以用不同的方式组合:

    var everything = [
        callInSequence, [
            [ callInSequence, [[waste, 5], [waste, 4]] ],
            [ callInParallel, [[waste, 3], [waste, 2]] ],
            [ callInSequence, [[waste, 1], [waste, 0]] ]
        ]
    ];
    
  • 上面的thisArg没有处理,每个函数都将针对全局对象(window)调用。我把实现它作为一个练习。有两种方法可以做到这一点,一种是使用.bind(),另一种是扩展callDef的定义。

不确定如果请求失败将如何工作,

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>

我假设$.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);