为什么 XMLHttpRequest 上传在 Firefox 中没有正常失败?
Why is XMLHttpRequest upload not failing properly in Firefox?
我正在实现一个文件上传器,用户可以使用 XMLHttpRequest
上传一个或多个文件。我不使用fetch
,因为我需要能够向用户提供关于上传进度的视觉反馈。
我遇到的问题是服务器在上传完成之前停止处理(例如,如果正在上传的文件太大,则关闭连接并出现 413 Payload Too Large
错误)。如果在使用 Safari 或 Chrome 时出现此类错误,他们将按我的意愿停止上传。
然而,在 Firefox 中,它似乎忽略了这一点并在停止之前重试了几次上传。
我的代码如下:
// Initialize a new request object.
let req = new XMLHttpRequest();
// Set expected response as JSON.
req.responseType = 'json';
// Set event handlers.
req.upload.onreadystatechange = function(e) { console.log(e.type); }
req.upload.onuploadstart = function(e) { console.log(e.type); }
req.upload.onprogress = function(e) { console.log(e.type); }
req.upload.onabort = function(e) { console.log(e.type); }
req.upload.onload = function(e) { console.log(e.type); }
req.upload.ontimeout = function(e) { console.log(e.type); }
req.upload.onuploadend = function(e) { console.log(e.type); }
// Open request, set request header.
req.open('POST', '/some-endpoint', true);
req.setRequestHeader('Content-type', 'multipart/form-data;boundary=---some-boundary---');
// Create FormData object to submit.
let fd = new FormData(formElement);
// Send data.
req.send(fd);
在 Safari 和 Chrome 中,当我上传一个太大而服务器无法接受的文件时,导致服务器关闭连接并返回 413 状态响应,事件按以下顺序触发:
loadstart
progress (multiple)
Failed to load resource (413 Request Entity Too Large)
如我所料。在 Firefox 中,事件按以下顺序触发:
loadstart
progress (multiple, ignoring connection closes and restarting upload multiple times)
loadend
Firefox 似乎不会在 loadend
事件之前触发 load
、error
、abort
或 timeout
事件,如XMLHttpRequest.upload
documentation
查看每个浏览器的开发工具的网络选项卡表明 Chrome 和 Safari 都识别服务器已响应 413,但 Firefox 未识别任何响应状态(即使在 loadend
).
版本为 Firefox Quantum 62.0b3(64 位)。 Safari 是 11.0.1。 Chrome 是 67.0.3396.99.
所以,问题是:为什么 Firefox 无法识别上传过程中发生服务器错误并取消上传,而 Safari 和 Chrome 可以? 和 有什么办法可以解决这个问题吗?
因为 这可能是 Firefox 中的错误,或与错误相关。
这并没有回答原来的问题。但是,它确实提供了一种解决方法,并希望为其他人提供一些可能具有启发性的信息。
Firefox、Safari 和 Chrome 在上传成功时(即当服务器在上传完成前没有发回响应或关闭连接时)都以相同的顺序触发事件。该顺序是:
readystatechange (readyState = 1)
loadstart
progress (1...n times)
load
loadend
readystatechange (readyState = 2)
readystatechange (readyState = 4)
...符合预期。
Safari 和 Chrome 在上传失败时(即当服务器关闭连接并发回响应时)以相同的顺序触发事件。该顺序是:
readystatechange (readyState = 1)
loadstart
progress (1...n times)
[the server responds with an error, which does *not* trigger an error event]
readystatechange (readyState = 2)
readystatechange (readyState = 3)
readystatechange (readyState = 4)
另一方面,Firefox 在上传失败时按以下顺序触发事件:
readystatechange (readyState = 1)
loadstart
progress (1...n times, including retrying from the start more than once when the server responds or closes the connection)
readystatechange (readyState = 2)
readystatechange (readyState = 3)
readystatechange (readyState = 4)
error
loadend
为了防止 Firefox 可能无缘无故地多次重新启动上传,我的解决方法是包含一个变量来跟踪之前加载的数量:
let prevLoaded = 0;
xhr.upload.addEventListener('progress', function(e) {
if (prevLoaded !== 0 && e.loaded <= prevLoaded) {
xhr.abort();
return;
}
prevLoaded = e.loaded;
}, false);
这会导致请求被取消。 Safari 和 Chrome 不会 运行 此代码,因为其他事件会先于它们触发。有了这段代码,Firefox 上传失败的事件触发顺序就变成了:
readystatechange (readyState = 1)
loadstart
progress (1...n times, but almost always stopping after the server closes the connection or responds with an error)
readystatechange (readyState = 4)
abort
loadend
我正在实现一个文件上传器,用户可以使用 XMLHttpRequest
上传一个或多个文件。我不使用fetch
,因为我需要能够向用户提供关于上传进度的视觉反馈。
我遇到的问题是服务器在上传完成之前停止处理(例如,如果正在上传的文件太大,则关闭连接并出现 413 Payload Too Large
错误)。如果在使用 Safari 或 Chrome 时出现此类错误,他们将按我的意愿停止上传。
然而,在 Firefox 中,它似乎忽略了这一点并在停止之前重试了几次上传。
我的代码如下:
// Initialize a new request object.
let req = new XMLHttpRequest();
// Set expected response as JSON.
req.responseType = 'json';
// Set event handlers.
req.upload.onreadystatechange = function(e) { console.log(e.type); }
req.upload.onuploadstart = function(e) { console.log(e.type); }
req.upload.onprogress = function(e) { console.log(e.type); }
req.upload.onabort = function(e) { console.log(e.type); }
req.upload.onload = function(e) { console.log(e.type); }
req.upload.ontimeout = function(e) { console.log(e.type); }
req.upload.onuploadend = function(e) { console.log(e.type); }
// Open request, set request header.
req.open('POST', '/some-endpoint', true);
req.setRequestHeader('Content-type', 'multipart/form-data;boundary=---some-boundary---');
// Create FormData object to submit.
let fd = new FormData(formElement);
// Send data.
req.send(fd);
在 Safari 和 Chrome 中,当我上传一个太大而服务器无法接受的文件时,导致服务器关闭连接并返回 413 状态响应,事件按以下顺序触发:
loadstart
progress (multiple)
Failed to load resource (413 Request Entity Too Large)
如我所料。在 Firefox 中,事件按以下顺序触发:
loadstart
progress (multiple, ignoring connection closes and restarting upload multiple times)
loadend
Firefox 似乎不会在 loadend
事件之前触发 load
、error
、abort
或 timeout
事件,如XMLHttpRequest.upload
documentation
查看每个浏览器的开发工具的网络选项卡表明 Chrome 和 Safari 都识别服务器已响应 413,但 Firefox 未识别任何响应状态(即使在 loadend
).
版本为 Firefox Quantum 62.0b3(64 位)。 Safari 是 11.0.1。 Chrome 是 67.0.3396.99.
所以,问题是:为什么 Firefox 无法识别上传过程中发生服务器错误并取消上传,而 Safari 和 Chrome 可以? 和 有什么办法可以解决这个问题吗?
因为
这并没有回答原来的问题。但是,它确实提供了一种解决方法,并希望为其他人提供一些可能具有启发性的信息。
Firefox、Safari 和 Chrome 在上传成功时(即当服务器在上传完成前没有发回响应或关闭连接时)都以相同的顺序触发事件。该顺序是:
readystatechange (readyState = 1)
loadstart
progress (1...n times)
load
loadend
readystatechange (readyState = 2)
readystatechange (readyState = 4)
...符合预期。
Safari 和 Chrome 在上传失败时(即当服务器关闭连接并发回响应时)以相同的顺序触发事件。该顺序是:
readystatechange (readyState = 1)
loadstart
progress (1...n times)
[the server responds with an error, which does *not* trigger an error event]
readystatechange (readyState = 2)
readystatechange (readyState = 3)
readystatechange (readyState = 4)
另一方面,Firefox 在上传失败时按以下顺序触发事件:
readystatechange (readyState = 1)
loadstart
progress (1...n times, including retrying from the start more than once when the server responds or closes the connection)
readystatechange (readyState = 2)
readystatechange (readyState = 3)
readystatechange (readyState = 4)
error
loadend
为了防止 Firefox 可能无缘无故地多次重新启动上传,我的解决方法是包含一个变量来跟踪之前加载的数量:
let prevLoaded = 0;
xhr.upload.addEventListener('progress', function(e) {
if (prevLoaded !== 0 && e.loaded <= prevLoaded) {
xhr.abort();
return;
}
prevLoaded = e.loaded;
}, false);
这会导致请求被取消。 Safari 和 Chrome 不会 运行 此代码,因为其他事件会先于它们触发。有了这段代码,Firefox 上传失败的事件触发顺序就变成了:
readystatechange (readyState = 1)
loadstart
progress (1...n times, but almost always stopping after the server closes the connection or responds with an error)
readystatechange (readyState = 4)
abort
loadend