Skip to content

Fetch

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

基本概念和用法

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

fetch() 强制接受一个参数,即要获取的资源的路径。它返回一个 Promise,该 Promise 会在服务器使用标头响应后,兑现为该请求的 Response——即使服务器的响应是 HTTP 错误状态。你也可以传一个可选的第二个参数 init配置对象。

从 fetch() 返回的 Promise 不会因 HTTP 的错误状态而被拒绝,即使响应是 HTTP 404 或 500。相反,它将正常兑现(ok 状态会被设置为 false),并且只有在网络故障或者有任何阻止请求完成时,才拒绝。除非你在 init 对象中设置(去包含)credentials,否则 fetch() 将不会发送跨源 cookie。

要中止未完成的 fetch(),甚至 XMLHttpRequest 操作,请使用 AbortController 和 AbortSignal 接口。

js
// 停止fetch 信号 signal.signal = AbortSignal对象
const signal = new AbortController()
// fetch兼容性检测
if (window.fetch) {
  fetch("http://example.com/movies.json",{
    method: "POST", // *GET, POST, PUT, DELETE, etc.
    mode: "cors", // no-cors, *cors, same-origin,navigate,websocket
    cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
    credentials: "same-origin", // include:即使跨域也会携带Cookie,此时响应的 Access-Control-Allow-Origin 不能使用通配符 "*" *same-origin, omit:省略的意思,浏览器不在请求中包含凭据
    headers: {
      "Content-Type": "application/json",
      // 'Content-Type': 'application/x-www-form-urlencoded',
      // 上传文件时,不设Content-Type,借助FormData对象
    },
    redirect: "follow", // manual, *follow, error
    referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify({name:'zhangjinxi'}), // body data type must match "Content-Type" header
    referrer:'baidu.com',
    signal:signal, // signal.abort() 可以停止fetch请求

  })
  .then((response) => response.json())
  .then((data) => console.log(data));
} else {
  // do something with XMLHttpRequest?
}

Fetch相关接口

对象描述
fetch()包含了 fetch() 方法,用于获取资源
Headers表示响应/请求的标头信息,允许你查询它们,或者针对不同的结果做不同的操作。
Request相当于一个资源请求对象
Response相当于请求的响应对象

逐行处理文本文件

从响应中读取的分块不是按行分割的,并且是 Uint8Array 数组类型(不是字符串类型)。如果你想通过 fetch() 获取一个文本文件并逐行处理它,那需要自行处理这些复杂情况。以下示例展示了一种创建行迭代器来处理的方法(简单起见,假设文本是 UTF-8 编码的,且不处理 fetch() 的错误)。

js
// generate生成器函数,返回值为迭代器,每读到一部分数据,通过yield返回,然后通过迭代拿到yield的数据,每次迭代一行数据。
async function* makeTextFileLineIterator(fileURL) {
  // TextDecoder 解码为字符串
  const utf8Decoder = new TextDecoder("utf-8");
  // 获取文件
  const response = await fetch(fileURL);
  // file文件响应的body,为readableStream类型,可读流。是个迭代器,通过read()方法,每次读取一部分数据。
  const reader = response.body.getReader();
  // 读取可读流已经加载到的内容
  let { value: chunk, done: readerDone } = await reader.read();
  // 对加载到的数据,解码为字符串
  chunk = chunk ? utf8Decoder.decode(chunk) : "";
  // 全局多行匹配换行符 \n \r,把数据根据换行符,分隔到数组result中
  const re = /\n|\r|\r\n/gm;
  // 总的数据中,开始截取数据的位置index
  let startIndex = 0;
  // 每次匹配换行符的结果,通过lastIndex,记录上次匹配字符的位置
  let result;
  // for循环进行持续的读取可读流加载的数据
  for (;;) {
    // 返回数组或者null。chunk:解析为字符串后,数组的总和。通过re.lastIndex,每次匹配chunk字符串中,lastIndex后面的一部分数据
    let result = re.exec(chunk);
    // 没有读取到换行
    if (!result) {
      // 读取数据完毕则结束循环
      if (readerDone) {
        break;
      }
      // 拷贝上次已经读取的数据
      let remainder = chunk.substr(startIndex);
      // 继续读取数据
      ({ value: chunk, done: readerDone } = await reader.read());
      // 已经读取的数据 + 新读取的数据进行解码
      chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : "");
      // 重置index,从头开始继续匹配
      startIndex = re.lastIndex = 0;
      // 此时还不够一行,不迭代数据。循环判断是否满足一行
      continue;
    }
    // 存在换行,通过迭代器返回这一行的数据
    yield chunk.substring(startIndex, result.index);
    // 重置startIndex为上次读取结束的位置
    startIndex = re.lastIndex;
  }
  // 读取结束后,如果startIndex小于数组总长度,返回剩余不足一行的部分数据
  if (startIndex < chunk.length) {
    // last line didn't end in a newline char
    yield chunk.substr(startIndex);
  }
}

async function run() {
  // 通过forOf进行迭代,拿到每次yeild的数据
  for await (let line of makeTextFileLineIterator(urlOfFile)) {
    processLine(line);
  }
}
run();

检测请求是否成功

如果遇到网络故障或服务端的 CORS 配置错误时,fetch() promise 将会 reject,带上一个 TypeError 对象。虽然这个情况经常是遇到了权限问题或类似问题。比如 404 不是一个网络故障。想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是否为 true。类似以下代码:

js

fetch("flowers.jpg")
  .then((response) => {
    // 4xx 和 5xx 错误,仍然会进入resolved成功回调里。在这里,需要进一步判断response.ok,是否成功响应数据。
    if (!response.ok) {
      throw new Error("Network response was not OK");
    }
    return response.blob();
  })
  .then((myBlob) => {
    // 成功响应数据
    myImage.src = URL.createObjectURL(myBlob);
  })
  .catch((error) => {
    // 网络错误、请求被取消。经过第一个then函数resolved回调里,throw 错误 4xx和5xx才会进入这里
    console.error("There has been a problem with your fetch operation:", error);
  });

自定义Request请求对象 & Headers请求头对象

除了传给 fetch() 一个资源的地址,你还可以通过使用 Request() 构造函数来创建一个 request 对象,更多是作为其他 API 操作结果返回的 Request 对象,比如 service worker 的 FetchEvent.request。可作为参数传给 fetch():

request对象的实例属性和方法

属性和方法描述
body主题内容的ReadableStream对象
bodyUsed请求是否被读取过
cache包含请求的缓存模式,default,reload,no-cache
credentials包含请求的凭据same-origin,include,omit
headers请求相关联的headers对象
method请求方式
mode请求模式,cors,no-cors,some-origin,navigate
signal返回与请求相关的AbortSignal
url请求的url
arrayBuffer()返回promise,resolved时,值为ArrayBuffer类型
blob()返回promise,resolved时,值为blob类型
clone()返回一个当前Request对象的副本
formData()返回promise,resolved时,值为FromData类型
json()返回promise,resolved时,值为json类型
text()返回promise,resolved时,值为文本类型

Request() 和 fetch() 接受同样的参数。你甚至可以传入一个已存在的 request 对象来创造一个拷贝:

js
// 构造Headers对象,用来更改Request对象headers请求头信息,Request和Response对象上都有Headers对象属性,可以拿到头部信息。具有guard守卫属性,web中不可用,配置是否可被更改。
// Headers对象api很像FormData对象 get set has delete append keys values entries forEach,具有iterate迭代器接口,可使用forOf遍历属性。传入不符合规定的头部会报错
const content = "Hello World";
const myHeaders = new Headers({
  "Content-Type": "text/plain",
  "Content-Length": content.length.toString(),
  "X-Custom-Header": "ProcessThisImmediately",
});
// 等同于如下:
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Content-Length", content.length.toString());
myHeaders.append("X-Custom-Header", "ProcessThisImmediately");

console.log(myHeaders.has("Content-Type")); // true
// set为设置,如果有同名属性未被覆盖,没有同名属性为新增
myHeaders.set("Content-Type", "text/html");
// append为追加,即使有同名属性也会添加,没有同名属性为新增
myHeaders.append("X-Custom-Header", "AnotherValue");

console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.get("X-Custom-Header")); // ['ProcessThisImmediately', 'AnotherValue']

myHeaders.delete("X-Custom-Header");
console.log(myHeaders.get("X-Custom-Header")); // null

const formData = new FormData(document.getElementById("login-form"));
formData.append('name','hello world')

// 构造Request对象:参数为url init配置对象
const myRequest = new Request("flowers.jpg", {
  method: "GET",
  headers: myHeaders,
  mode: "cors",
  cache: "default",
  body:formData
});

fetch(myRequest)
  .then((response) =>{
    // 检查头部字段是否正确
    const contentType = response.headers.get("content-type");
    if (!contentType || !contentType.includes("application/json")) {
      throw new TypeError("Oops, we haven't got JSON!");
    }
    return response.blob()
  })
  .then((myBlob) => {
    myImage.src = URL.createObjectURL(myBlob);
  });

// Request() 和 fetch() 接受同样的参数。你甚至可以传入一个已存在的 request 对象来创造一个拷贝:
const anotherRequest = new Request(myRequest, myInit);

这个很有用,因为 request 和 response bodies设计成了 stream 的方式,所以它们只能被读取一次。创建一个拷贝就可以再次使用 request/response 了,当然也可以使用不同的 init 参数。创建拷贝必须在读取 body 之前进行,而且读取拷贝的 body 也会将原始请求的 body 标记为已读。

Response对象

Response 实例是在 fetch() 处理完 promise 之后返回的。Response最长用到三个属性

属性名描述信息
status整数,为response的HTTP状态码,默认200
statusText字符串,与HTTP状态码相对应,简短解释
ok布尔值,检查response的状态码是否在200-299这个范围内。用来判断是否请求成功
headersHeaders对象
redirected是否来自一个重定向,是的话,他的URL列表将会有多个条目
typeResponse的类型,basic,cors
urlURL
body暴露一个ReadableStream类型的body内容
bodyUsed是否被读取过
clone()创建一个Response对象的副本
error()返回一个绑定了网络错误的新的Response对象副本
redirect()用一个url创建一个新的Response对象副本
arrayBuffer()返回一个被解析为 ArrayBuffer 格式的 Promise 对象
formData()返回一个被解析为 FormData 格式的 Promise 对象
json()返回一个被解析为 json 格式的 Promise 对象
text()返回一个被解析为 text 格式的 Promise 对象

Response对象也可以通过js创建,但只有在 ServiceWorkers 中使用 respondWith() 方法并提供了一个自定义的 response 来接受 request 时才真正有用:

js
const myBody = new Blob();
// ServiceWorkers中拦截fetch请求,命中缓存时,可以自定义响应信息
addEventListener("fetch", (event) => {
  // 自定义响应内容和响应头信息
  event.respondWith(
    // Response构造方法接受两个可选参数——response 的 body 和一个初始化对象(与Request() 所接受的 init 参数类似)。
    new Response(myBody, {
      headers: { "Content-Type": "text/plain" },
    }),
  );
});

Response静态方法 error() 只是返回了错误的 response。与此类似地,redirect() 只是返回了一个可以重定向至某 URL 的 response。这些也只与 Service Worker 有关。

Body对象

不管是请求还是响应都能够包含 body 对象。body 也可以是以下任意类型的实例。

  • ArrayBuffer
  • ArrayBufferView(Uint8Array等)
  • Blob(File对象继承自Blob)
  • string
  • URLSearchParams
  • FormData

Body 类定义了以下方法(这些方法都被 Request 和 Response所实现)以获取 body 内容。这些方法都会返回一个被解析后的 Promise 对象和数据。

  • arrayBuffer()
  • blob()
  • formData()
  • json()
  • text()

相比于 XHR,这些方法让非文本化数据的使用更加简单。

request 和 response(包括 fetch() 方法)都会试着自动设置 Content-Type。如果没有设置 Content-Type 值,发送的请求也会自动设值。

根据 MIT 许可证发布