XMLHttpRequest
XMLHttpRequest
(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest
在 AJAX 编程中被大量使用。 尽管名称如此,XMLHttpRequest
可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP),尽管可能受到更多出于安全等原因的限制。
优点
- 兼容性好,经典中的经典 API
- 现阶段绝大多数网络请求还是由 XHR 作为底层来负责,所以绝大部分传统需求 XHR 都能满足
缺点
接口设计较差,使用起来较为繁琐(上层应用解决了这一问题)- 对新特性的支持或者配合较差
- Stream API
- Cache API
- Service Worker
常规使用
interface AjaxOptions {
url: string
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
timeout: number
responseType: XMLHttpRequest['responseType']
data: any
headers: object
onProgress: XMLHttpRequest['onprogress']
onUploadProgress: XMLHttpRequest['upload']['onprogress']
}
const NOOP = () => { }
const DEFAULT_OPTIONS: AjaxOptions = {
url: '#',
method: 'GET',
timeout: 3000,
responseType: 'text',
data: {},
headers: {},
onProgress: NOOP,
onUploadProgress: NOOP
}
function optionsAssign(before: AjaxOptions, after: Partial<AjaxOptions>): AjaxOptions {
return Object.assign(before, after, { header: Object.assign(before.headers, after.headers) })
}
export default function ajaxWithXHR(options: Partial<AjaxOptions> = {}) {
const { url, method, timeout, responseType, data, headers, onProgress, onUploadProgress } = optionsAssign(DEFAULT_OPTIONS, options)
const XHR = new XMLHttpRequest()
return new Promise((resolve, reject) => {
XHR.open(method, url)
// responseType
XHR.responseType = responseType
// timeout
XHR.timeout = timeout
// headers
for (const key in headers) {
if (Object.prototype.hasOwnProperty.call(headers, key)) {
const value = headers[key];
XHR.setRequestHeader(key, value)
}
}
// onProgress handlers
XHR.onprogress = onProgress
XHR.upload.onprogress = onUploadProgress
// reject
XHR.onerror = () => reject(new Error('error'))
XHR.onabort = () => reject(new Error('abort_error'))
XHR.ontimeout = () => reject(new Error('timeout_error'))
// resolve
XHR.onloadend = () => {
const { status } = XHR
if (status >= 200 && status < 300 || status === 304) {
resolve(XHR)
} else {
reject(new Error('status_error'))
}
}
// send request
try {
XHR.send(data)
} catch (error) {
reject(new Error('send_error'))
}
})
}
Fetch
Fetch API 提供了一个获取资源的接口(包括跨域请求)。任何使用过
XMLHttpRequest
的人都能轻松上手,而且新的 API 提供了更强大和灵活的功能集。Fetch 提供了对
Request
和Response
(以及其他与网络请求有关的)对象的通用定义。使之今后可以被使用到更多地应用场景中:无论是 service worker、Cache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。它同时还为有关联性的概念,例如CORS和HTTP原生头信息,提供一种新的定义,取代它们原来那种分离的定义。
发送请求或者获取资源,需要使用
WindowOrWorkerGlobalScope.fetch()
方法。它在很多接口中都被实现了,更具体地说,是在Window
和WorkerGlobalScope
接口上。因此在几乎所有环境中都可以用这个方法获取到资源。
fetch()
必须接受一个参数——资源的路径。无论请求成功与否,它都返回一个 Promise 对象,resolve 对应请求的Response
。你也可以传一个可选的第二个参数init
(参见Request
)。一旦
Response
被返回,就可以使用一些方法来定义内容的形式,以及应当如何处理内容(参见Body
)。你也可以通过
Request()
和Response()
的构造函数直接创建请求和响应,但是我们不建议这么做。他们应该被用于创建其他 API 的结果(比如,service workers 中的FetchEvent.respondWith
)。
优点
- fetch 使用 Promise,大大精简了写法,优化了接口
- 采用模块化设计,有
Request
、Response
、Headers
来负责各自模块的描述,比 XHR 跟清晰 - 采用 Stream API 来处理数据,可以分块读取,减少内存消耗,提高网站性能,尤其是对于大文件的请求
- 与 service worker 和 Cache API 配合可以自定义 Response,实现特殊功能
缺点
- 兼容性较差,可以使用 polyfill 解决
- 对于 主动中断请求,超时自动中断,请求进度获取 的支持奇差
总结
XHR 和 Fetch 并非对立,笔者个人认为 Fetch 虽然带着其现代性和前瞻性,但仍旧是半成品,在如 Service Worker 场景中有奇效,但是对于基本的中断请求等需求的支持很糟糕,实现起来很是麻烦,同时 XHR API 冗杂的问题也完全可以由上层应用解决,所以笔者认为 fetch 的时代还没有真正来临,现在还是 XHR 的天下,但 fetch 的未来绝对光明。
如何选型的问题,笔者个人认为正常业务场景的时候 XHR 绝对够用,尤其是使用其上层应用如 axios 或者其他应用的时候,绝对够用,所以 XHR 还没有到退休的时候,那么问题就是什么时候我们去选择使用 fetch 呢,个人认为主要有如下几个场景可以使用 fetch
- 需要对 Service Worker 和 Cache API 进行深度使用的时候,其自定义请求和响应可以玩出很多花活
- 大文件下载的时候,fetch 对于 Stream 的使用可以大大的提高内容处理的性能
总结的结果就是,优先使用 XHR,在 XHR 无法覆盖的场景下可以使用 fetch,两者混合使用