Fetch API 全了解

Fetch API 提供了一个获取资源的接口(包括跨域请求)。

概念和用法

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

发送请求或者获取资源,需要使用 WindowOrWorkerGlobalScope.fetch() 方法。

它在很多接口中都被实现了,更具体地说,是在 Window 和 WorkerGlobalScope 接口上。因此在几乎所有环境中都可以用这个方法获取到资源。

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

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

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

中止 fetch

浏览器已经开始为 AbortController 和 AbortSignal 接口(也就是Abort API)添加实验性支持,允许像 Fetch 和 XHR 这样的操作在还未完成时被中止 。

Fetch 接口

  • WindowOrWorkerGlobalScope.fetch()
    包含了fetch() 方法,用于获取资源。
  • Headers
    相当于 response/request 的头信息,可以使你查询到这些头信息,或者针对不同的结果做不同的操作。
  • Request
    相当于一个资源请求。
  • Response
    相当于请求的响应

Fetch mixin

Body

提供了与 response/request 中的 body 有关的方法,可以定义它的内容形式以及处理方式。

使用 Fetch

Fetch API 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应。

Fetch API提供了一个全局 fetch() 方法。

:zap: 一个基本的 fetch 请求设置起来很简单。看看下面的代码:

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

fetch语法

Promise<Response> fetch(input[, init]);

//类型定义
declare function fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
type RequestInfo = Request | string;
interface RequestInit {
    body?: BodyInit | null;
    cache?: RequestCache;
    credentials?: RequestCredentials;
    headers?: HeadersInit;
    integrity?: string;
    keepalive?: boolean;
    method?: string;
    mode?: RequestMode;
    redirect?: RequestRedirect;
    referrer?: string;
    referrerPolicy?: ReferrerPolicy;
    signal?: AbortSignal | null;
    window?: null;
}

相关类型:

interface Body {
    readonly body: ReadableStream<Uint8Array> | null;
    readonly bodyUsed: boolean;
    arrayBuffer(): Promise<ArrayBuffer>;
    blob(): Promise<Blob>;
    formData(): Promise<FormData>;
    json(): Promise<any>;
    text(): Promise<string>;
}

interface Request extends Body {
    readonly cache: RequestCache;
    readonly credentials: RequestCredentials;
    readonly destination: RequestDestination;
    readonly headers: Headers;
    readonly integrity: string;
    readonly keepalive: boolean;
    /** Returns request's HTTP method, which is "GET" by default. */
    readonly method: string;
    readonly mode: RequestMode;
    readonly redirect: RequestRedirect;
    readonly referrer: string;
    readonly referrerPolicy: ReferrerPolicy;
    readonly signal: AbortSignal;
    /** Returns the URL of request as a string. */
    readonly url: string;
    clone(): Request;
}

declare var Request: {
    prototype: Request;
    new(input: RequestInfo, init?: RequestInit): Request;
};

/** This Fetch API interface represents the response to a request. */
interface Response extends Body {
    readonly headers: Headers;
    readonly ok: boolean;
    readonly redirected: boolean;
    readonly status: number;
    readonly statusText: string;
    readonly type: ResponseType;
    readonly url: string;
    clone(): Response;
}

declare var Response: {
    prototype: Response;
    new(body?: BodyInit | null, init?: ResponseInit): Response;
    error(): Response;
    redirect(url: string | URL, status?: number): Response;
};

参数详解

?input

定义要获取的资源。这可能是:

  • 一个 USVString 字符串,包含要获取资源的 URL。一些浏览器会接受 blob:data: 作为 schemes.
  • 一个 Request 对象。

init 可选

一个配置项对象,包括所有对请求的设置。可选的参数有:

  • method: 请求使用的方法,如 GET、POST。
  • headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
  • body: 请求的 body 信息:可能是一个 BlobBufferSource (en-US)FormDataURLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
  • mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
  • credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie , 必须提供这个选项, 从 Chrome 50 开始, 这个属性也可以接受 FederatedCredential (en-US) 实例或是一个 PasswordCredential (en-US) 实例。
  • cache: 请求的 cache 模式: defaultno-storereloadno-cache force-cache 或者 only-if-cached
  • redirect: 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在Chrome中默认使用follow(Chrome 47之前的默认值是manual)。
  • referrer: 一个 USVString 可以是 no-referrer、``client或一个 URL。默认是 client。
  • referrerPolicy: 指定了HTTP头部referer字段的值。可能为以下值之一: no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
  • integrity: 包括请求的 subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。

返回值

一个 Promise,resolve 时回传 Response 对象。

例外

类型 描述
AbortError 请求被AbortController.abort()终止。
TypeError Firefox 43开始,如果fetch()接收到含有用户名和密码的URL(例如http://user:password@example.com),它将会抛出一个TypeError

Fetch使用示例

请求参数

// Example POST method implementation:
async function postData(url = '', data = {}) {
  // Default options are marked with *
  const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    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(data) // body data type must match "Content-Type" header
  });
  return response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
  .then(data => {
    console.log(data); // JSON data parsed by `data.json()` call
  });

上传json数据

即发送post请求

const data = { username: 'example' };

fetch('https://example.com/profile', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
  console.log('Success:', data);
})
.catch((error) => {
  console.error('Error:', error);
});

上传文件

可以通过 HTML <input type="file" /> 元素,FormData()fetch() 上传文件。

//上传单个文件

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.then(result => {
  console.log('Success:', result);
})
.catch(error => {
  console.error('Error:', error);
});

//上传多个文件
//可以通过 HTML <input type="file" multiple /> 元素,FormData() 和 fetch() 上传文件。
const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');

formData.append('title', 'My Vegas Vacation');
for (let i = 0; i < photos.files.length; i++) {
  formData.append(`photos_${i}`, photos.files[i]);
}

fetch('https://example.com/posts', {
  method: 'POST',
  body: formData,
})
.then(response => response.json())
.then(result => {
  console.log('Success:', result);
})
.catch(error => {
  console.error('Error:', error);
});

逐行处理文本文件

从响应中读取的分块不是按行分割的,并且是 Uint8Array 数组类型(不是字符串类型)。如果你想通过 fetch() 获取一个文本文件并逐行处理它,那需要自行处理这些复杂情况。

以下示例展示了一种创建行迭代器来处理的方法(简单起见,假设文本是 UTF-8 编码的,且不处理 fetch() 的错误)。

async function* makeTextFileLineIterator(fileURL) {
  const utf8Decoder = new TextDecoder('utf-8');
  const response = await fetch(fileURL);
  const reader = response.body.getReader();
  let { value: chunk, done: readerDone } = await reader.read();
  chunk = chunk ? utf8Decoder.decode(chunk) : '';

  const re = /\n|\r|\r\n/gm;
  let startIndex = 0;
  let result;

  for (;;) {
    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) : '');
      startIndex = re.lastIndex = 0;
      continue;
    }
    yield chunk.substring(startIndex, result.index);
    startIndex = re.lastIndex;
  }
  if (startIndex < chunk.length) {
    // last line didn't end in a newline char
    yield chunk.substr(startIndex);
  }
}

async function run() {
  for await (let line of makeTextFileLineIterator(urlOfFile)) {
    processLine(line);
  }
}

run();

检测请求是否成功

如果遇到网络故障或服务端的 CORS 配置错误时,fetch() promise 将会 reject,带上一个 TypeError 对象。

虽然这个情况经常是遇到了权限问题或类似问题——比如 404 不是一个网络故障。

想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是否为 true。

类似以下代码:

fetch('flowers.jpg')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not OK');
    }
    return response.blob();
  })
  .then(myBlob => {
    myImage.src = URL.createObjectURL(myBlob);
  })
  .catch(error => {
    console.error('There has been a problem with your fetch operation:', error);
  });

HTTP 404 状态并不被认为是网络错误。

自定义请求对象

const myHeaders = new Headers();
const myRequest = new Request('flowers.jpg', {
  method: 'GET',
  headers: myHeaders,
  mode: 'cors',
  cache: 'default',
});

fetch(myRequest)
  .then(response => response.blob())
  .then(myBlob => {
    myImage.src = URL.createObjectURL(myBlob);
  });

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

const anotherRequest = new Request(myRequest, myInit);

备注: clone() 方法也可以用于创建一个拷贝。它和上述方法一样,如果 request 或 response 的 body 已经被读取过,那么将执行失败。区别在于, clone() 出的 body 被读取不会导致原 body 被标记为已读取。

Headers

Fetch APIHeaders 接口允许您对HTTP请求和响应头执行各种操作。 这些操作包括检索,设置,添加和删除。

一个Headers对象具有关联的头列表,它最初为空,由零个或多个键值对组成。你可以使用 append() 方法添加 之类的方法添加到此

一个Headers对象也有一个关联的guard,它具有不可变的值,requestrequest-no-corsresponsenone。 这会影响 set(), delete(), 和append() 方法 改变header.

可以通过 Request.headersResponse.headers 属性检索一个Headers对象, 并使用 Headers.Headers() 构造函数创建一个新的Headers 对象.

一个实现了Headers 的对象可以直接用于 for...of 结构中, 而不是 entries(): for (var p of myHeaders) 等价于 for (var p of myHeaders.entries()).

构造函数

Headers.Headers()

创建一个新的Headers对象.

var myHeaders = new Headers(init);

init 可选

通过一个包含任意 HTTP headers 的对象来预设你的 Headers. 可以是一个ByteString 对象; 或者是一个已存在的 Headers 对象.

Headers方法

Headers.append()

给现有的header添加一个值, 或者添加一个未存在的header并赋值.

Headers.delete()

从Headers对象中删除指定header.

Headers.entries()

以 迭代器的形式返回Headers对象中所有的键值对.

Headers.get()

ByteString 的形式从Headers对象中返回指定header的全部值.

Headers.has()

以布尔值的形式从Headers对象中返回是否存在指定的header.

Headers.keys()

迭代器的形式返回Headers对象中所有存在的header名.

Headers.set()

替换现有的header的值, 或者添加一个未存在的header并赋值.

Headers.values()

以迭代器的形式返回Headers对象中所有存在的header的值.

Headers.set()将会用新的值覆盖已存在的值, 但是Headers.append()会将新的值添加到已存在的值的队列末尾

:zap: 示例

//构造一个空的Headers
let myHeaders = new Headers();

myHeaders.append('Content-Type', 'text/xml');

myHeaders.get('Content-Type');
// should return 'text/xml'

// 通过init构造
var httpHeaders = { 'Content-Type' : 'image/jpeg', 'Accept-Charset' : 'utf-8', 'X-My-Custom-Header' : 'Zeke are cool' };
var myHeaders = new Headers(httpHeaders);

//通过init属性将一个已存在的Headers对象来创建另一个新的Headers对象
var secondHeadersObj = new Headers(myHeaders);
secondHeadersObj.get('Content-Type'); 
// Would return 'image/jpeg' — it inherits it from the first headers object

Request 对象

Fetch API 的 Request接口?用来表示资源请求。

构造器

Request.Request()
创建一个新的 Request 对象。

var myRequest = new Request(input[, init]);

input

定义你想要fetch的资源。可以是下面两者之一:

  • 一个直接包含你希望 fetch 的资源的 URL 的 USVString
  • 一个Request对象。

init 可选

**一个可选对象,包含希望被包括到请求中的各种自定义选项。**可用的选项如下:

  • method: 请求的方法,例如:GET, POST。
  • headers: 任何你想加到请求中的头,其被放在Headers对象或内部值为ByteString 的对象字面量中。
  • body: 任何你想加到请求中的body,可以是Blob, BufferSource (en-US), FormData, URLSearchParams, USVString,或ReadableStream对象。注意GETHEAD 请求没有body。
  • mode: 请求的模式, 比如 cors, no-cors, same-origin, 或 navigate。默认值为 cors
  • credentials: 想要在请求中使用的credentials:: omit, same-origin, 或 include。默认值应该为omit。但在Chrome中,Chrome 47 之前的版本默认值为 same-origin ,自Chrome 47起,默认值为include。
  • cache: 请求中想要使用的 cache mode
  • redirect: 对重定向处理的模式: follow, error, or manual。在Chrome中,Chrome 47 之前的版本默认值为 manual ,自Chrome 47起,默认值为follow。
  • referrer: 一个指定了no-referrer, client, 或一个 URL的 USVString 。默认值是about:client
  • integrity: 包括请求的 subresource integrity 值 (e.g., sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=).

属性

Request实现了Body, 所以它还具有以下属性可用:

方法

Request实现 Body, 因此它也有以下方法可用:

注意:这些Body功能只能运行一次; 随后的调用将通过空strings/ ArrayBuffers解析.

:zap: 示例

// 使用url构造
const myRequest = new Request('http://localhost/flowers.jpg');
const myURL = myRequest.url; // http://localhost/flowers.jpg
const myMethod = myRequest.method; // GET
const myCred = myRequest.credentials; // omit

// 使用request
fetch(myRequest)
  .then(response => response.blob())
  .then(blob => {
    myImage.src = URL.createObjectURL(blob);
  });

// 传入两个参数
const myRequest = new Request('http://localhost/api', {method: 'POST', body: '{"foo":"bar"}'});

const myURL = myRequest.url; // http://localhost/api
const myMethod = myRequest.method; // POST
const myCred = myRequest.credentials; // omit
const bodyUsed = myRequest.bodyUsed;

Response 对象

Fetch API 的 Response 接口呈现了对一次请求的响应数据。

构造函数

let myResponse = new Response(body, init);

body 可选

一个定义 response 中 body 的对象. 可以为 null ,或是以下其中一个:

init 可选

一个参数(options)对象,包含要应用到 response 上的任何自定义设置. 可能参数(options)是:

  • status: response 的状态码, 例如:200.
  • statusText: 和状态码关联的状态消息, 例如: OK.
  • headers: 你想加到 response 上的任何 headers, 包含了一个 Headers 对象或满足对象语法的 ByteString key/value 对 (详见 HTTP headers).

例如:

var myBlob = new Blob();
var init = { "status" : 200 , "statusText" : "SuperSmashingGreat!" };
var myResponse = new Response(myBlob,init);

属性

  • Response.headers 只读

    包含此 Response 所关联的 Headers 对象。

  • Response.ok 只读

    包含了一个布尔值,标示该 Response 成功(HTTP 状态码的范围在 200-299)。

  • Response.redirected 只读

    表示该 Response 是否来自一个重定向,如果是的话,它的 URL 列表将会有多个条目。

  • Response.status 只读

    包含 Response 的状态码 (例如 200 表示成功)。

  • Response.statusText 只读

    包含了与该 Response 状态码一致的状态信息(例如,OK对应 200)。

  • Response.type 只读

    包含 Response 的类型(例如,basiccors)。

  • Response.url 只读

    包含 Response 的URL。

  • Response.useFinalURL

    包含了一个布尔值,来标示这是否是该 Response 的最终 URL。

Response 实现了 Body 接口,所以以下属性亦可用:

方法

Response 实现了 Body 接口,所以以下方法同样可用:

  • Body.arrayBuffer() (en-US)

    读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 ArrayBuffer 格式的 Promise 对象。

  • Body.blob() (en-US)

    读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 Blob 格式的 Promise 对象。

  • Body.formData() (en-US)

    读取Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 FormData 格式的 Promise 对象。

  • Body.json() (en-US)

    读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 JSON 格式的 Promise 对象。

  • Body.text() (en-US)

    读取 Response 对象并且将它设置为已读(因为 Responses 对象被设置为了 stream 的方式,所以它们只能被读取一次),并返回一个被解析为 USVString 格式的 Promise 对象。

:zap: 示例

const image = document.querySelector('.my-image');
fetch('flowers.jpg').then(function(response) {
  return response.blob();
}).then(function(blob) {
  const objectURL = URL.createObjectURL(blob);
  image.src = objectURL;
});



//Body.json() (en-US),reponse继承body
//json()返回一个被解析为 JSON 格式的 Promise 对象。
const myList = document.querySelector('ul');
const myRequest = new Request('products.json');

fetch(myRequest)
  .then(response => response.json())
  .then(data => {
    for (const product of data.products) {
      let listItem = document.createElement('li');
      listItem.appendChild(
        document.createElement('strong')
      ).textContent = product.Name;
      listItem.append(
        ` can be found in ${
          product.Location
        }. Cost: `
      );
      listItem.appendChild(
        document.createElement('strong')
      ).textContent = `£${product.Price}`;
      myList.appendChild(listItem);
    }
  })
  .catch(console.error);

Fetch支持特性检测

Fetch API 的支持情况,可以通过检测 Headers, Request, Responsefetch() 是否在 WindowWorker 域中来判断。例如:

if (window.fetch) {
  // run my fetch request here
} else {
  // do something with XMLHttpRequest?
}