jQuery - .always() 回调触发太快

Posted

技术标签:

【中文标题】jQuery - .always() 回调触发太快【英文标题】:jQuery - .always() callback firing too soon 【发布时间】:2015-08-27 17:28:08 【问题描述】:

我正在开发一个客户端 JS 应用程序,该应用程序应该读取 CSV 文件,每行进行几次 API 调用,然后将结果写回 CSV。我坚持的部分是如何编排请求并在所有完成后触发功能。这是我目前所拥有的:

var requests = [];

// loop through rows
addresses.forEach(function (address, i) 
    // make request
    var addressRequest = $.ajax(
            dataType: 'json',
            url: 'http://api.com/addresses/' + address,
            success: function (data, textStatus, jqXhr)  APP.didGetAddressJson(data, i, jqXhr) ,
            error: function (jqXhr, textStatus, errorThrown)  APP.didFailToGetAddressJson(errorThrown, i) ,
        );
    requests.push(addressRequest);

    // make some more requests (handled by other success functions)
);

// leggo
$.when.apply($, requests).done(APP.didFinishGeocoding);

问题在于,如果其中一行抛出 404,则不会调用 done 函数。我将它切换到always,现在它被调用了,但不是在最后——如果我将每个回调的执行记录到控制台,它通常在中间的某个地方。但是,如果我编辑 CSV 所以没有错误,它会按预期在最后被调用。我是否在这里做了一些让always 提前触发的事情?

更新:难道只是控制台正在记录它out of order?

【问题讨论】:

中间是什么? AJAX 请求不一定按照它们发送的顺序完成。 forEach() 在 jQuery 上? developer.mozilla.org/en-US/docs/Web/javascript/Reference/… @Barmar 我的意思是如果我将每个回调记录到控制台。更新了问题。 阅读api.jquery.com/jquery.when 的段落,它解释了传递多个延迟对象时它是如何工作的。特别是:该方法将在所有 Deferred 解决后立即解决其 master Deferred,或者在其中一个 Deferred 被拒绝时立即拒绝 master Deferred。 所以.done() 在任何时候都会被调用的 AJAX 调用失败,否则当它们都成功时。 【参考方案1】:

您需要防止错误将$.when.apply($, requests) 返回的承诺沿错误路径发送。

这可以通过以下方式实现:

.then() 链接到您的$.ajax() 调用,而不是将“成功”和“错误”处理程序指定为$.ajax() 选项。 通过转换为成功来处理错误(因为这是 jQuery,您必须从错误处理程序返回已解决的承诺)。

这种方法还允许您控制最终传送到APP.didFinishGeocoding()的数据

在一些假设下,您的代码的大致形状应如下所示:

function foo () //assume there's an outer function wrapper 
    var errorMarker = '**error**';

    var requests = addresses.map(function (address, i) 
        return $.ajax(
            dataType: 'json',
            url: 'http://api.com/addresses/' + address
        ).then(function (data, textStatus, jqXhr)  //success handler
            return APP.didGetAddressJson(data, i, jqXhr); //whatever APP.didGetAddressJson() returns will appear as a result at the next stage.
        , function (jqXhr, textStatus, errorThrown)  // error handler
            APP.didFailToGetAddressJson(errorThrown, i);
            return $.when(errorMarker);//errorMarker will appear as a result at the next stage - but can be filtered out.
        );
        // make some more requests (handled by other success functions)
    );

    return $.when.apply($, requests).then(function() 
        //first, convert arguments to an array and filter out the errors
        var results = Array.prototype.slice.call(arguments).filter(function(r) 
            return r !== errorMarker;
        );

        //then call APP.didFinishGeocoding() with the filtered results as individual arguments.
        return APP.didFinishGeocoding.apply(APP, results);

        //alternatively, call APP.didFinishGeocoding() with the filtered results as an array.
        //return APP.didFinishGeocoding(results);
    );

根据需要进行调整。

【讨论】:

【参考方案2】:

尝试通过 whenAll 函数传递已解决、被拒绝的 jQuery 承诺对象,在 whenAll 完成时过滤 .then() 中已解决、被拒绝的承诺对象。另见Jquery Ajax prevent fail in a deferred sequential loop


(function ($) 
    $.when.all = whenAll;
    function whenAll(arr) 
        "use strict";
        var deferred = new $.Deferred(),
            args = !! arr 
                   ? $.isArray(arr) 
                     ? arr 
                     : Array.prototype.slice.call(arguments)
                       .map(function (p) 
                         return p.hasOwnProperty("promise") 
                         ? p 
                         : new $.Deferred()
                           .resolve(p, null, deferred.promise())
                       ) 
                   : [deferred.resolve(deferred.promise())],
            promises = 
                "success": [],
                  "error": []
            , doneCallback = function (res) 
                promises[this.state() === "resolved" 
                         || res.textStatus === "success" 
                         ? "success" 
                         : "error"].push(res);
                return (promises.success.length 
                       + promises.error.length) === args.length 
                       ? deferred.resolve(promises) 
                       : res
            , failCallback = function (res) 
                // do `error` notification , processing stuff
                // console.log(res.textStatus);
                promises[this.state() === "rejected" 
                        || res.textStatus === "error" 
                        ? "error" 
                        : "success"].push(res);
                return (promises.success.length 
                       + promises.error.length) === args.length 
                       ? deferred.resolve(promises) 
                       : res
            ;
        $.map(args, function (promise, index) 
            return $.when(promise).always(function (data, textStatus, jqxhr) 
                return (textStatus === "success") 
                    ? doneCallback.call(jqxhr, 
                        data: data,
                        textStatus: textStatus 
                                    ? textStatus 
                                    : jqxhr.state() === "resolved" 
                                      ? "success" 
                                      : "error",
                        jqxhr: jqxhr
                      ) 
                    : failCallback.call(data, 
                        data: data,
                        textStatus: textStatus,
                        jqxhr: jqxhr
                      )
            )
        );
        return deferred.promise()
    ;
(jQuery));

例如

var request = function (url) 
    return $.ajax(
                   url: "http://api.com/addresses/" + url, 
                   dataType: "json"
           )
    
, addresses = [
    ["/echo/json/"], // `success`
    ["/echo/jsons/"], // `error`
    ["/echo/json/"], // `success`
    ["/echo/jsons/"], // `error`
    ["/echo/json/"] // `success`
];

$.when.all(
  $.map(addresses, function (address) 
    return request(address)
  )
)
.then(function (data) 
    console.log(data);
    // filter , process responses
    $.each(data, function(key, value) 
        if (key === "success") 
           value.forEach(function(success, i) 
              console.log(success, i);
              APP.didGetAddressJson(success.data, i, success.jqxhr);
            )
         else             
           value.forEach(function(error, i) 
              console.log(error, i);
              APP.didFailToGetAddressJson(error.jqxhr, i)
          )
        
    )
, function (e) 
    console.log("error", e)
);

jsfiddle http://jsfiddle.net/guest271314/ev4urod1/

【讨论】:

以上是关于jQuery - .always() 回调触发太快的主要内容,如果未能解决你的问题,请参考以下文章

jQuery中deferred对象的使用

如何通过jQuery停止鼠标离开太快而缩短背景图像过渡

jQuery.getJSON 不会触发回调

JQuery Plugin - 通过回调触发内部函数

回调不会在 JQuery Mobile 多页面中触发

从零开始学_JavaScript_系列——jquery(基础,选择器,触发条件,动画,回调函数)