权衡是否使用 XMLHttpRequest 与 Fetch 及其对 Ajax 的现代表现?我们比较两种选择的优缺点。
XMLHttpRequest
1999 年作为 IE5.0 ActiveX 组件发布的第一个实现。在此之前,有一些方法可以在不刷新整页的情况下从服务器中提取数据,但它们通常依赖于笨拙的技术,例如<script>
注入或第三方插件。微软XMLHttpRequest
为他们的 Outlook 电子邮件客户端开发了一个基于浏览器的替代方案。
XMLHttpRequest
直到 2006 年才成为 Web 标准,但它已在大多数浏览器中实现。它在 Gmail (2004) 和 Google Maps (2005) 中的采用导致了 Jesse James Garrett 的 2005 年文章AJAX: A New Approach to Web Applications。新术语明确了开发人员的重点。
AJAX到 Ajax
AJAX 是异步 JavaScript 和 XML 的助记符。“异步”肯定是,但是:
- JavaScript 是可能的,虽然 VBScript 和 Flash 是选项
- 有效负载不需要是 XML,尽管这在当时很流行。可以使用任何数据格式,如今,通常首选 JSON。
我们现在使用“Ajax”作为任何客户端进程的通用术语,它从服务器获取数据并动态更新 DOM 而无需刷新整个页面。Ajax 是大多数 Web 应用程序和单页应用程序 (SPA) 的核心技术。
极端 XMLHttpRequest
http://domain/service
以下 JavaScript 代码显示了一个基本的HTTP GET 请求XMLHttpRequest(通常缩写为 XHR):
let xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain/service');
// request state change event
xhr.onreadystatechange = function() {
// request completed?
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// request successful - show response
console.log(xhr.responseText);
}
else {
// request error
console.log('HTTP error', xhr.status, xhr.statusText);
}
};
// start request
xhr.send();
该XMLHttpRequest对象具有许多其他选项、事件和响应属性。例如,可以设置和检测以毫秒为单位的超时:
// set timeout
xhr.timeout = 3000; // 3 seconds
xhr.ontimeout = () => console.log('timeout', xhr.responseURL);
并且progress
事件可以报告长时间运行的文件上传:
// upload progress
xhr.upload.onprogress = p => {
console.log( Math.round((p.loaded / p.total) * 100) + '%') ;
}
选项的数量可能令人眼花缭乱,早期的实现XMLHttpRequest
有一些跨浏览器的不一致。出于这个原因,大多数库和框架都提供了 Ajax 包装函数来处理复杂性,例如jQuery.ajax()方法:
// jQuery Ajax
$.ajax('http://domain/service')
.done(data => console.log(data))
.fail((xhr, status) => console.log('error:', status));
快进到Fetch
FetchXMLHttpRequest
API是. 通用的Headers、Request和Response接口提供了一致性,而 Promises 允许更轻松的链接和异步/等待而无需回调。上面的 XHR 示例可以转换为更简单的基于 Fetch 的代码,甚至可以解析返回的 JSON:
fetch(
'http://domain/service',
{ method: 'GET' }
)
.then( response => response.json() )
.then( json => console.log(json) )
.catch( error => console.error('error:', error) );
Fetch 干净、优雅、易于理解,并且在PWA Service Workers中大量使用。为什么不使用它来代替古老的 XMLHttpRequest?
不幸的是,Web 开发从来没有那么明确。Fetch 还不能完全替代 Ajax 技术……
浏览器支持
Fetch API 得到了相当好的支持,但在所有版本的 Internet Explorer 中都会失败。使用早于 2017 年的 Chrome、Firefox 和 Safari 版本的用户也可能会遇到问题。这些用户可能只占您用户群的一小部分……或者可能是主要客户。在开始编码之前务必检查!
此外,与成熟的 XHR 对象相比,Fetch API 更新并接收更多的持续更改。这些更新不太可能破坏代码,但预计未来几年会进行一些维护工作。
默认无 Cookie
与 不同XMLHttpRequest
的是,并非所有 Fetch 实现都会发送 cookie,因此您的应用程序的身份验证可能会失败。可以通过更改第二个参数中传递的启动选项来解决问题,例如
fetch(
'http://domain/service',
{
method: 'GET',
credentials: 'same-origin'
}
)
错误不被拒绝
令人惊讶的是,诸如404 Page Not Found或500 Internal Server Error
之类的 HTTP 错误不会导致 Fetch Promise 拒绝;.catch()
永远不会运行。它通常会在response.ok
状态设置为 false 的情况下解决。
仅当请求无法完成时才会发生拒绝,例如网络故障。这会使错误捕获的实现更加复杂。
不支持超时
Fetch 不支持超时,只要浏览器选择,请求就会继续。需要进一步的代码来将 Fetch 包装在另一个 Promise 中,例如
// fetch with a timeout
function fetchTimeout(url, init, timeout = 3000) {
return new Promise((resolve, reject) => {
fetch(url, init)
.then(resolve)
.catch(reject);
setTimeout(reject, timeout);
}
}
… 或者Promise.race()当获取或超时首先完成时使用 to which 解决,例如
Promise.race([
fetch('http://url', { method: 'GET' }),
new Promise(resolve => setTimeout(resolve, 3000))
])
.then(response => console.log(response))
中止Fetch
用函数结束 XHR 请求很容易xhr.abort()
,如果有必要,可以用xhr.onabort
函数检测这样的事件。
中止 Fetch 几年来是不可能的,但现在实现AbortController API的浏览器支持它。这会触发一个信号,该信号可以传递给 Fetch 启动对象:
const controller = new AbortController();
fetch(
'http://domain/service',
{
method: 'GET'
signal: controller.signal
})
.then( response => response.json() )
.then( json => console.log(json) )
.catch( error => console.error('Error:', error) );
可以通过调用来中止获取controller.abort();
。Promise 拒绝,因此.catch()
调用该函数。
没有进度
在撰写本文时,Fetch 不支持进度事件。因此,不可能报告文件上传或类似的大型表单提交的状态。
XMLHttpRequest 与 Fetch API?
最终,选择权在您手中……除非您的应用程序有需要上传进度条的 IE 客户端。
对于更简单的 Ajax 调用,XMLHttpRequest
它是较低级别的、更复杂的,并且您将需要包装函数。不幸的是,一旦您开始考虑超时、调用中止和错误捕获的复杂性,Fetch 也会如此。
您可以选择Fetch polyfill和Promise polyfill,这样就可以在 IE 中编写 Fetch 代码。但是,XHR 被用作后备;并非每个选项都会按预期工作,例如无论设置如何都会发送 cookie。
Fetch是未来。但是,API 相对较新,它不提供所有 XHR 功能,而且有些选项很麻烦。在接下来的几年中谨慎使用它。