如何使jquery的.each循环等待响应?

3

当我调用一个调用AWS脚本并等待AWS响应的第二个函数时,如何使.each循环迭代等待?
这是我的.each循环:

$('.subscriptionStatus').each(function(i, obj) {
    var sku = $(this).attr('data-sku');
    subscriptionStatus(sku, function(response) {
        var messageDiv = $(this).closest('.subscriptionMessage');

        var innerHtml = '';
        if (response){
            innerHtml = 'Successful';
        }else{
            innerHtml = 'Failed';
        }
        messageDiv.html(innerHtml);
    }//wait until 
}

对于每个迭代,我都会调用一个函数,在其中调用AWS Lambda中的脚本,并需要等待其响应,然后继续迭代:

function subscriptionStatus( sku, callback ){
    var lambda = new AWS.Lambda();
    lambda.invoke({
        FunctionName: 'MyFunction',
        Payload: JSON.stringify({
            "sku":sku
        })
    }, function(err, data){
        //response from AWS Lambda

        var success = data.Payload;
        if( success == 'true'){
            //NEED TO PASS TRUE BACK TO THE CURRENT ITERATION OF THE EACH LOOP
            callback(true); //correct way?
        }else{
            //NEED TO PASS FALSE BACK TO THE CURRENT ITERATION OF THE EACH LOOP
            callback(false); //correct way?
        }
    });
}

我的问题是迭代没有等待。
我的代码有什么问题?


1
你必须停止使用.each,因为它永远不会等待异步操作。 - Kevin B
我并不是要表现得高人一等或者什么的,但你应该一定要读一下这篇文章:http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/。它非常幽默地描绘了你在这里遇到的问题。问题基本上是你在同步循环中调用异步函数。所以唯一的方法就是在循环中阻塞每个调用,直到它返回。看起来 Lambda 函数对于同步执行有了新的支持,但文档比较模糊:http://docs.aws.amazon.com/lambda/latest/dg/getting-started-custom-events.html。 - Milimetric
@Miss_poker 除非 lambda 有同步选项,否则你无法做到这一点。请注意,该选项会导致您的应用程序在循环期间冻结,包括所有 CSS 动画、GIF、加载栏和用户交互。 - Kevin B
一个递归函数应该这样做:在异步操作完成后,调用自身并引用数组中的下一项。 - Kevin B
@Miss_poker:我在搜索时找到的链接就是这些文档,但我对lambda不太熟悉。如果你可以使用纯AJAX访问AWS,jquery有一个async: false选项:http://api.jquery.com/jquery.ajax/。基本上,你必须在lambda中找到相同类型的行为,或者更改你的逻辑以接受异步响应(也是可能的,只需保留结果列表,并在AWS回调时追加即可)。 - Milimetric
显示剩余3条评论
2个回答

1
最简单的方法是使用 Promises 和 Deferreds(在 jQuery 中)。
你的代码大致如下:
function subscriptionStatus( sku ){
    var $dfd = jQuery.Deferred();
    var lambda = new AWS.Lambda();
    lambda.invoke({
        FunctionName: 'MyFunction',
        Payload: JSON.stringify({
            "sku":sku
        })
    }, function(err, data){
        if( data.Payload == 'true'){
             $dfd.resolve();
        }else{
            $dfd.reject();
        }
    });
    return $dfd.promise();
}

你现在可以做:

subscriptionStatus(<sku>)
    .done(function() {

    }).fail(function() {

    });

现在进入花式部分:如何在您的.each循环中实现这一点?这取决于它是否需要按正确顺序进行,或者您只需等待它们全部完成。如果您只需等待所有操作完成而无需特定顺序:
// no particular order
var deferreds = []; // does nothing yet
$('.bla').each(function(){
    var $this = $(this);
    var $msgDiv = $this.closest('.subscriptionMessage');
    var sku = $this.attr('data-sku');
    var $dfd = subscriptionStatus(sku)
       .done(function() { $msgDiv.html('Successful'); })
       .fail(function() { $msgDiv.html('Failed'); });
    deferreds.push($dfd);
});
jQuery.when.apply(jQuery,deferreds)
    .done(function() { alert('all successful'); })
    .fail(function() { alert('any of the requests was not succesful'); })
    .always(function() { alert('they are all done'); });

0

你最终解决了这个问题吗?我看到你的代码还有另一个问题,与Lambda无关。你传递给subscriptionStatus的回调函数中的this值不会是.subscriptionStatus元素。因此,messageDiv很可能始终是一个空的jQuery对象。你可以将其记录下来进行验证。

要解决这个问题,可以在.each()中保存对this的引用,像这样:

$('.subscriptionStatus').each(function(i, obj) {
  var _this = $(this); // save reference
  var sku = _this.attr('data-sku');
  subscriptionStatus(sku, function(response) {
    // now _this has a reference to the actual jQuery object wrapping the .subscriptionStatus element
    var messageDiv = _this.closest('.subscriptionMessage');

    var innerHtml = '';
    if (response){
        innerHtml = 'Successful';
    }else{
        innerHtml = 'Failed';
    }
    messageDiv.html(innerHtml);
  }//wait until 
}

我怀疑这将解决你的问题,除非你真的想要阻塞直到每个响应返回(这会影响渲染时间)。如果是这样,你应该研究一下Promise。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接