XMLHttpRequest与Fetch API


XMLHttpRequest

目前常见的主流浏览器度支持XMLHttpRequest,下面列举一个常见的请求示例:

const xhr = new XMLHttpRequest();
xhr.open("GET", "/service");

// state change event
xhr.onreadystatechange = () => {
  // is request complete?
  if (xhr.readyState !== 4) return;

  if (xhr.status === 200) {
    // request successful xhr.responseText/xhr.responseXml
    console.log(JSON.parse(xhr.responseText));
  } else {
    // request not successful
    console.log("HTTP error", xhr.status, xhr.statusText);
  }
};

// start request
xhr.send()

onreadystatechange 回调函数在请求的生命周期中运行好几次;XMLHttpRequest 对象的 readyState 属性则返回当前状态:

  • 0 (UNSET) – XMLHttpRequest被创建,且open() 方法未调用
  • 1(OPENED)- open() 已经被调用
  • 2(HEADERS_RECEIVED)- send() 已经被调用,且响应头部和状态已经获得
  • 3(LOADING)- 下载中,response已经包含部分数据
  • 4(DONE)- 请求完成

onreadystatechange

readyState属性发生变化时,会调用 onreadystatechange函数

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
    // 0 1 ""
    // 200 2 ""
    // 200 3 "第一次输出"
    // 200 3 "第一次输出延迟2秒后的输出"
    // 200 3 "第一次输出延迟2秒后的输出延迟4秒后的输出"
    // 200 4 "第一次输出延迟2秒后的输出延迟4秒后的输出"
    console.log(this.status, this.readyState, this.response);
}

xhr.open('GET', url);
xhr.send();

status

返回的状态码

statusText

返回的状态

response

响应内容,数据类型通过responseType定义

responseType

描述
为空时,默认和text相同
arraybufferresponse是一个 ArrayBuffer 对象
blobresponse是一个 Blob 对象
documentresponse是一个HTML Document 或者 XML Document
jsonresponse是一个js对象
textresponse是一个DOMString`对象

timeout

设置请求超时时间,当超时发生后,请求的readyState会变为4(DONE), status由已接受到的200变为0,respose置为空。触发timeout事件。

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function () {
    // 0 1 ""
    // 200 2 ""
    // 200 3 "第一次输出"
    // 0 4 ""
    console.log(this.status, this.readyState, this.response);
}
xhr.addEventListener('timeout', function () {
    // timeout 0 4
    console.log('timeout', this.status, this.readyState, this.response);
});
xhr.addEventListener('error', function () {
    console.log('error', this.status, this.readyState, this.response);
});

xhr.open('GET', url);
xhr.timeout = 1000;
xhr.send();

withCredentials

默认值是false,在跨域请求时,不携带cookie,且响应的Set-Cookie不生效。设置为true时,跨域请求会带上cookie,且响应的Set-Cookie会生效。对同源请求,该属性不生效。

// 验证withCredentials可以发送cookies、设置cookies
var xhr = new XMLHttpRequest();
var url = 'http://localhost:8090/setcookie?c=' + encodeURIComponent('random=' + Math.random());

xhr.onreadystatechange = function () {
    console.log(this.status, this.readyState, this.response);
}

xhr.open('GET', url);
xhr.withCredentials = true;
xhr.send();
// 请求完成后,在开发者面板可以看到,域名localhost:8090下的cookies中存在由响应设置的random值

注意

  • withCredentials依然要遵循浏览器的安全策略,不同域名下的cookie是隔离的,不能相互访问对方域名下的cookie
  • 响应头需要设置Access-Control-Allow-OriginAccess-Control-Allow-Credentials,否则跨域请求会失败
self.set_header('Access-Control-Allow-Origin', 'http://localhost:8210')
self.set_header('Access-Control-Allow-Credentials', 'true') 

setRequestHeader

设置HTTP请求头,需要在open()函数调用和send()函数调用之间调用。 考虑到安全问题,浏览器会禁止用户设置某些请求头。被禁止的请求头包括

getResponseHeader

获取HTTP响应头

Fetch

Fetch是一个全局函数,用来从网络上获取资源,具有两个参数resource,init。返回结果是Promise,请求结束时,返回Response对象。

Fetch 是一个现代基于 promise 的 Ajax 请求 API,首次出现于 2015 年,在大多数浏览器中都得到了支持。它不是基于 XMLHttpRequest 构建的,并且用更简洁的语法提供了更好的一致性。下面的 Promise 链函数与上面的 XMLHttpRequest 例子相同:

fetch("/service", { method: "GET" })
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch((err) => console.error("error:", err));

或者你可以使用 async/await:

try {
  const res = await fetch("/service", { method: "GET" }),
    json = await res.json();

  console.log(json);
} catch (err) {
  console.error("error:", err);
}

resource

资源的路径 或者Request对象

init

是一个初始化配置对象,具体参数配置如下

method

请求方式

headers

请求头,考虑到安全问题,浏览器会禁止用户设置某些请求头。Headers 对象提供了一个简单的接口来设置请求中的头信息或获取响应中的头信息

// set request headers
const headers = new Headers();
headers.set("X-Requested-With", "ajax");
headers.append("Content-Type", "text/xml");

const request = new Request("/service", {
  method: "POST",
  headers,
});

const res = await fetch(request);

// examine response headers
console.log(res.headers.get("Content-Type"));

body

请求体,可以是Blob,BufferSource FormDataURLSearchParamsUSVStringReadableStream。需要注意,GETHEAD请求不能设置body

mode

请求的模式,如 corsno-cors 或者 same-origin

  • same-origin 发送的是同源请求
  • no-cors 发送的是同源请求或简单的跨域请求
  • cors 发送的是非简单的跨域请求,会先发送预检请求

credentials

一个枚举值,可选的值有omitsame-origin或者include。默认是same-origin,当发起请求时,只有同源的情况下,会带上cookie,跨域时不带cookie。设置omit时,任何情况下都不带cookie。设置include的情况下,同源情况下带上cookie,跨域时也带cookie

cache

用来控制资源的缓存策略,可选的值是defaultno-storereloadno-cacheforce-cacheonly-if-cached

  • default  如果有一个新的 (未过期的) 匹配,则使用浏览器缓存;如果没有,浏览器会发出一个带条件的请求来检查资源是否已改变,并在必要时会发出新的请求
  • no-store:总是发送正常请求,且不缓存任何请求
  • reload:总是发送正常请求(不检查缓存),再用请求结果更新缓存
  • no-cache:有缓存则发送条件请求、无缓存则发送正常请求,再用请求结果更新缓存
  • force-cache:有缓存则强制使用缓存(不发送任何请求)、无缓存则发送正常请求再用请求结果更新缓存
  • only-if-cached:仅在请求模式为 mode="same-origin" 有效;在不与 mode="same-origin" 冲突的情况下,当重定向模式为 redirect="follow" 时,若发生重定向,且该重定向指向的请求有缓存,则该请求同样也遵循该缓存模式

redirect

请求被重定向时的处理策略

  • follow (自动重定向)
  •  error (如果产生重定向将自动终止并且抛出一个错误)
  • manual (手动处理重定向)

referrer

指定请求的来源,一个 USVString 可以是 no-referrerclient 或一个 URL。默认是 client

referrerPolicy

 指定了 HTTP 头部 referer 字段的值。可能为以下值之一:no-referrer、 no-referrer-when-downgradeoriginorigin-when-cross-origin、 unsafe-url

数据流

XMLHttpRequest 将整个响应读入内存缓冲区,但是 fetch() 可以流式传输请求和响应数据,这是一项新技术,流允许你在发送或接收时处理更小的数据块。例如,你可以在完全下载前处理数兆字节文件中的信息,下面的示例将传入的(二进制)数据块转换为文本,并将其输出到控制台。在较慢的连接上,你会看到更小的数据块在较长的时间内到达

const response = await fetch("/service"),
  reader = response.body
    .pipeThrough(new TextDecoderStream())
    .getReader();

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(value);
}

XMLHttpRequest优势

  • 兼容性更好,主流浏览器支持
  • 超时支持,XMLHttpRequest 对象提供了一个 timeout 属性,可以将其设置为请求自动终止前允许运行的毫秒数;如果超时,就触发一个 timeout 事件来处理
  • 进度支持,我们可以监控请求的进度,通过将一个处理程序附加到 XMLHttpRequest 对象的进度事件上。这在上传大文件(如照片)时特别有用:
const xhr = new XMLHttpRequest();

// progress event
xhr.upload.onprogress = (p) => {
  console.log(Math.round((p.loaded / p.total) * 100) + "%");
};
  • 终止支持,运行中的请求可以通过 XMLHttpRequest 的 abort() 方法取消,如有必要,可以附加一个 abort 事件来处理:
const xhr = new XMLHttpRequest();
xhr.open("GET", "/service");
xhr.send();

// ...

xhr.onabort = () => console.log("aborted");
xhr.abort();

你可以中止一个 fetch(),但它不是那么直接,需要一个 AbortController 对象:

const controller = new AbortController();

fetch("/service", {
  method: "GET",
  signal: controller.signal,
})
  .then((res) => res.json())
  .then((json) => console.log(json))
  .catch((error) => console.error("Error:", error));

// abort request
controller.abort();
  • 更显式的故障检测: 当开发人员第一次使用 fetch() 时,假设一个 HTTP 错误,如 404 Not Found 或 500 Internal Server error 将触发 Promise 拒绝并运行相关的 catch() 块,这似乎是合乎逻辑的,但事实并非如此:Promise 成功地解决了这些响应,只有当网络没有响应或请求被中断时,才会发生拒绝。fetch() 的 Response 对象提供了 status 和 ok 属性,但并不总是显式地需要检查它们,XMLHttpRequest 更明确,因为单个回调函数处理每一个结果:你应该在每个示例中都看到 stuatus 检查

Leave a Reply

Your email address will not be published. Required fields are marked *