【问题标题】:jQuery - .always() callback firing too soonjQuery - .always() 回调触发太快
【发布时间】: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 上?
  • @Barmar 我的意思是如果我将每个回调记录到控制台。更新了问题。
  • 阅读api.jquery.com/jquery.when 的段落,它解释了传递多个延迟对象时它是如何工作的。特别是:该方法将在所有 Deferred 解决后立即解决其 master Deferred,或者在其中一个 Deferred 被拒绝时立即拒绝 master Deferred。 所以.done() 在任何时候都会被调用的 AJAX 调用失败,否则当它们都成功时。

标签: javascript jquery jquery-deferred


【解决方案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/

    【讨论】:

      猜你喜欢
      • 2010-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多