XMLHttpRequest
XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest在 AJAX 编程中被大量使用。 尽管名称如此,XMLHttpRequest可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP),尽管可能受到更多出于安全等原因的限制。
优点
- 兼容性好,经典中的经典 API
- 现阶段绝大多数网络请求还是由 XHR 作为底层来负责,所以绝大部分传统需求 XHR 都能满足
缺点
接口设计较差,使用起来较为繁琐(上层应用解决了这一问题)- 对新特性的支持或者配合较差
- Stream API
- Cache API
- Service Worker
常规使用
interface AjaxOptions {url: stringmethod: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'timeout: numberresponseType: XMLHttpRequest['responseType']data: anyheaders: objectonProgress: 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)// responseTypeXHR.responseType = responseType// timeoutXHR.timeout = timeout// headersfor (const key in headers) {if (Object.prototype.hasOwnProperty.call(headers, key)) {const value = headers[key];XHR.setRequestHeader(key, value)}}// onProgress handlersXHR.onprogress = onProgressXHR.upload.onprogress = onUploadProgress// rejectXHR.onerror = () => reject(new Error('error'))XHR.onabort = () => reject(new Error('abort_error'))XHR.ontimeout = () => reject(new Error('timeout_error'))// resolveXHR.onloadend = () => {const { status } = XHRif (status >= 200 && status < 300 || status === 304) {resolve(XHR)} else {reject(new Error('status_error'))}}// send requesttry {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,两者混合使用
