【问题标题】:How to sequence groups of async functions without nesting (jquery $.when .then)如何在不嵌套的情况下对异步函数组进行排序(jquery $.when .then)
【发布时间】:2015-08-12 23:30:23
【问题描述】:

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

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

    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>');
    		});
    	});
    });
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"&gt;&lt;/script&gt;

以下是我目前想出的,但它在每个部分都有一种尴尬的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>');
});
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"&gt;&lt;/script&gt;

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

我想理想情况下,我希望这样的事情能奏效:

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

【问题讨论】:

  • return $.when 解决问题了吗?
  • @guest271314 从技术上讲,它确实解决了嵌套问题,但是在每个函数调用之间有).then(function(){return $.when( 似乎相当麻烦,我不确定如果请求失败它会如何工作,所以我试图找到“标准”的方式来做到这一点。
  • .then(doneCallbacks, failCallbacks)api.jquery.com/deferred.then/…
  • @guest271314 是的,我想我在想“whens”甚至没有“thens”,但我想你可以在每个上添加一个 then 并且只将它传递给 failcallbacks跨度>
  • 查看帖子以了解添加.fail().then(success, err) 回调的一种方法。

标签: javascript jquery asynchronous promise


【解决方案1】:

如何从不同的角度解决问题:您希望将什么输入映射到什么输出?让我们看看我们是否可以将您的问题概括为这一点。


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

每个操作都可以由一个函数和一个参数列表表示,等待适当的时间到来时执行。让我们将这个抽象命名为 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单独执行叶子callDefs,并通过callInParallel并行执行每组。

缺少的位是一个callInSequence 函数,它在前一个完成后执行每个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');
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"&gt;&lt;/script&gt;

注意事项:

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

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

【讨论】:

  • 你以前用lisp编程吗?!我真的很喜欢这个答案。我认为像这样非常直接的东西应该在 javascript 或 jquery 中本地实现,以消除笨拙的 lamda 函数。
  • 我想像: $.async([waste,5],[waste,4]).async([waste,3],[waste,2]);甚至 $.async('waste(5)','waste(4)').async('waste(3)','waste(2)');或者也许本机等待{废物(5);废物(4); }
  • 不,我从来没有用 Lisp 编程过。 :) 如您所见,使用纯 JS 已经可以实现很多事情,并非所有事情都需要原生语言结构。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    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);
    &lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 感谢您对如何添加失败条件的描述,但您是否普遍认为这是一种处理异步函数调用排序组的简单而好方法?
    • @AwokeKnowing 是的,直截了当。 “好”可以是主观的;可以多种方式组成;只要作者对实现和返回的预期结果感到满意,也许对作者来说“好”有什么重要的?在没有任何描述的情况下查看会出现每个$.when 是不同的功能“组”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-13
    相关资源
    最近更新 更多