XMLHttpRequest

XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequestAJAX 编程中被大量使用。 尽管名称如此,XMLHttpRequest 可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP),尽管可能受到更多出于安全等原因的限制。

优点

  • 兼容性好,经典中的经典 API
  • 现阶段绝大多数网络请求还是由 XHR 作为底层来负责,所以绝大部分传统需求 XHR 都能满足

缺点

  • 接口设计较差,使用起来较为繁琐(上层应用解决了这一问题)
  • 对新特性的支持或者配合较差
    • Stream API
    • Cache API
    • Service Worker

常规使用

  1. interface AjaxOptions {
  2. url: string
  3. method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
  4. timeout: number
  5. responseType: XMLHttpRequest['responseType']
  6. data: any
  7. headers: object
  8. onProgress: XMLHttpRequest['onprogress']
  9. onUploadProgress: XMLHttpRequest['upload']['onprogress']
  10. }
  11. const NOOP = () => { }
  12. const DEFAULT_OPTIONS: AjaxOptions = {
  13. url: '#',
  14. method: 'GET',
  15. timeout: 3000,
  16. responseType: 'text',
  17. data: {},
  18. headers: {},
  19. onProgress: NOOP,
  20. onUploadProgress: NOOP
  21. }
  22. function optionsAssign(before: AjaxOptions, after: Partial<AjaxOptions>): AjaxOptions {
  23. return Object.assign(before, after, { header: Object.assign(before.headers, after.headers) })
  24. }
  25. export default function ajaxWithXHR(options: Partial<AjaxOptions> = {}) {
  26. const { url, method, timeout, responseType, data, headers, onProgress, onUploadProgress } = optionsAssign(DEFAULT_OPTIONS, options)
  27. const XHR = new XMLHttpRequest()
  28. return new Promise((resolve, reject) => {
  29. XHR.open(method, url)
  30. // responseType
  31. XHR.responseType = responseType
  32. // timeout
  33. XHR.timeout = timeout
  34. // headers
  35. for (const key in headers) {
  36. if (Object.prototype.hasOwnProperty.call(headers, key)) {
  37. const value = headers[key];
  38. XHR.setRequestHeader(key, value)
  39. }
  40. }
  41. // onProgress handlers
  42. XHR.onprogress = onProgress
  43. XHR.upload.onprogress = onUploadProgress
  44. // reject
  45. XHR.onerror = () => reject(new Error('error'))
  46. XHR.onabort = () => reject(new Error('abort_error'))
  47. XHR.ontimeout = () => reject(new Error('timeout_error'))
  48. // resolve
  49. XHR.onloadend = () => {
  50. const { status } = XHR
  51. if (status >= 200 && status < 300 || status === 304) {
  52. resolve(XHR)
  53. } else {
  54. reject(new Error('status_error'))
  55. }
  56. }
  57. // send request
  58. try {
  59. XHR.send(data)
  60. } catch (error) {
  61. reject(new Error('send_error'))
  62. }
  63. })
  64. }

Fetch

Fetch API 提供了一个获取资源的接口(包括跨域请求)。任何使用过 XMLHttpRequest 的人都能轻松上手,而且新的 API 提供了更强大和灵活的功能集。

Fetch 提供了对 RequestResponse (以及其他与网络请求有关的)对象的通用定义。使之今后可以被使用到更多地应用场景中:无论是 service worker、Cache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。

它同时还为有关联性的概念,例如CORS和HTTP原生头信息,提供一种新的定义,取代它们原来那种分离的定义。

发送请求或者获取资源,需要使用 WindowOrWorkerGlobalScope.fetch() 方法。它在很多接口中都被实现了,更具体地说,是在 WindowWorkerGlobalScope 接口上。因此在几乎所有环境中都可以用这个方法获取到资源。

fetch() 必须接受一个参数——资源的路径。无论请求成功与否,它都返回一个 Promise 对象,resolve 对应请求的 Response。你也可以传一个可选的第二个参数 init(参见 Request)。

一旦 Response 被返回,就可以使用一些方法来定义内容的形式,以及应当如何处理内容(参见 Body)。

你也可以通过 Request()Response() 的构造函数直接创建请求和响应,但是我们不建议这么做。他们应该被用于创建其他 API 的结果(比如,service workers 中的 FetchEvent.respondWith)。

优点

  • fetch 使用 Promise,大大精简了写法,优化了接口
  • 采用模块化设计,有 RequestResponseHeaders 来负责各自模块的描述,比 XHR 跟清晰
  • 采用 Stream API 来处理数据,可以分块读取,减少内存消耗,提高网站性能,尤其是对于大文件的请求
  • 与 service worker 和 Cache API 配合可以自定义 Response,实现特殊功能

缺点

  • 兼容性较差,可以使用 polyfill 解决
  • 对于 主动中断请求超时自动中断请求进度获取 的支持奇差

总结

XHR 和 Fetch 并非对立,笔者个人认为 Fetch 虽然带着其现代性和前瞻性,但仍旧是半成品,在如 Service Worker 场景中有奇效,但是对于基本的中断请求等需求的支持很糟糕,实现起来很是麻烦,同时 XHR API 冗杂的问题也完全可以由上层应用解决,所以笔者认为 fetch 的时代还没有真正来临,现在还是 XHR 的天下,但 fetch 的未来绝对光明。

如何选型的问题,笔者个人认为正常业务场景的时候 XHR 绝对够用,尤其是使用其上层应用如 axios 或者其他应用的时候,绝对够用,所以 XHR 还没有到退休的时候,那么问题就是什么时候我们去选择使用 fetch 呢,个人认为主要有如下几个场景可以使用 fetch

  1. 需要对 Service Worker 和 Cache API 进行深度使用的时候,其自定义请求和响应可以玩出很多花活
  2. 大文件下载的时候,fetch 对于 Stream 的使用可以大大的提高内容处理的性能

总结的结果就是,优先使用 XHR,在 XHR 无法覆盖的场景下可以使用 fetch,两者混合使用

参考