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() 回调触发太快的主要内容,如果未能解决你的问题,请参考以下文章