如何与 async/await 一起使用 Fetch
Fetch API 已经成为前端应用中获取资源的原生方式。
在这篇文章中,我将展示如何用 async/await 语法使用 Fetch API 的常见场景。目的是让你对如何获取数据、处理获取错误、取消获取请求等有信心。
开始之前,推荐熟悉 async/await 语法。在下面的例子中将广泛使用它。
fetch() 简介
Fetch API 通过网络访问资源。通过创建 HTTP 请求(GET,POST),下载和上传文件。
开始请求时,执行规范函数 fetch()
:
const response = await fetch(resource[, options]);
该函数接收两个参数:
resource
:一个 URL 字符串,或者一个 Request 对象;options
:一个配置对象,对象包含method
('GET''
,'POST''
),headers
,body
,credentials
等等属性,更多属性参数。
执行 fetch()
启动一个请求并返回一个承诺。当请求完成后,用 Response 解决承诺。如果由于一些网络问题导致请求失败,则拒绝该承诺。
async/await
语法非常适合 fetch()
,因为它简化了对承诺的工作。
例如,让我们做一个获取一些电影的请求:
async function fetchMovies() {
const response = await fetch('/movies');
// 等待直到请求完成
console.log(response);
}
fetchMovies()
是一个异步函数,因为它用 async
关键字标记。
await fetch('/movies')
开始向 '/movies'
URL 发出 HTTP 请求。由于存在 await
关键字,异步函数被暂停,直到请求完成。
当请求完成后,响应会被分配给请求的响应对象。让我们在下一节看看如何从响应中提取有用的数据,如 JSON 或纯文本。
请求 JSON 数据
fetch()
返回的 Response
对象是多种数据格式的通用占位符。下面是你如何从响应对象中以 JSON 数据的形式获取电影。
async function fetchMoviesJSON() {
const response = await fetch('/movies');
const movies = await response.json(); return movies;
}
fetchMoviesJSON().then((movies) => {
movies; // fetched movies
});
response.json()
是 Response 对象上的一个方法,让你从响应中提取 JSON 对象。该方法返回一个 promise,所以你必须等待 JSON 结果返回:await response.json()
。
response
对象提供了很多有用的方法:
response.json()
返回一个解析为 JSON 对象的 promise;response.text()
返回一个解析为文本内容的 promise;response.formData
返回一个解析为 FormData 对象的 promise;response.blob()
返回一个解析为 Blog 的 promise;response.arrayBuffer()
返回一个解析为 ArrayBuffer 的 promise。
处理 fetch 错误
当我在了解 fetch()
的时候,我很惊讶,当服务器返回一个不好的 HTTP 状态时,比如 404
或 502
,fetch()
不会抛出一个错误。
让我们尝试访问服务器上一个不存在的页面 '/oops'
。正如预期的那样,这种请求以 404
响应状态结束:
async function fetchMovies404() {
const response = await fetch('/oops');
response.ok; // => false
response.status; // => 404
const text = await response.text();
return text;
}
fetchMovies404().then((text) => {
text; // => 'Page not found'
});
当获取 URL '/oops'
时,服务器的响应是状态 404
和文本 'Page not found'
。令人惊讶的是,fetch()
并不会因为 URL 缺失而抛出错误,而是将其视为一个已完成的 HTTP 请求。
fetch()
只有在请求无法发出或响应无法获取时才会拒绝。这可能是因为网络问题:没有网络连接,找不到主机,服务器没有响应。
幸运的是,response.ok
属性可以让你区分 HTTP 响应状态的好坏。只有当响应状态在 200
到 299
之间时,该属性才会被设置为 true
。
在上面的例子中,response.ok
属性为 false
,因为响应状态为 404
。
如果你想对一个坏的 HTTP 状态(在 200-299
范围之外)抛出一个错误,检查 response.ok
属性的值并手动抛出一个错误:
async function fetchMoviesBadStatus() {
const response = await fetch('/oops');
if (!response.ok) { const message = `An error has occured: ${response.status}`; throw new Error(message); }
const movies = await response.json();
return movies;
}
fetchMoviesBadStatus().catch((error) => {
error.message; // 'An error has occurred: 404'
});
取消 fetch 请求
为了取消一个 丰田车请求,你需要添加 AbortController 工具。使用 AbortController
需要 3 步:
// Step 1: instantiate the abort controller
const controller = new AbortController();
// Step 2: make the fetch() aware of controller.signal
fetch(..., { signal: controller.signal });
// Step 3: call to cancel the request
controller.abort();
在下面的例子中,但点击按钮 “Cancel” 时,一个 fetch()
请求被取消:
async function fetchMoviesWithCancel(controller) {
const response = await fetch('/movies', {
signal: controller.signal,
});
const movies = await response.json();
return movies;
}
const controller = new AbortController();
cancelButton.addEventListener('click', () => {
controller.abort();
});
fetchMoviesWithCancel(controller).catch((error) => {
error.name; // => 'AbortError'
});
const controller = new AbortController()
创建一个 AbortController 的实例。然后 controller.signal
属性最为 fetch 请求的参数:fetch(..., { signal: controller.signal })
。
当 controller.abort()
在按钮的点击事件函数中被执行时,取消请求。
当 fetch 请求被取消,fetch()
函数失败并返回拒绝错误(AbortError)的 promise。
并发 fetch 请求
执行 Promise.all()
函数发起并发 fetch 请求。让我们并发请求电影和分类的两个接口:
async function fetchMoviesAndCategories() {
const [moviesResponse, categoriesResponse] = await Promise.all([ fetch('/movies'), fetch('/categories'), ]);
const movies = await moviesResponse.json();
const categories = await categoriesResponse.json();
return {
movies,
categories,
};
}
fetchMoviesAndCategories().then(({ movies, categories }) => {
movies; // fetched movies
categories; // fetched categories
});
await Promise.all([...])
并发开始 fetch 请求,并等待直到所有的请求完成。
拦截 fetch 请求
有时候,你可能需要在发送请求之前,或者在收到响应之后进行工作:这被命名为拦截。
拦截的一个例子是处理 fetch 错误:如果响应状态不在 200
到 299
的范围内,抛出一个错误。
fetch()
API 并没有提供任何拦截请求的功能。这是可以理解的,因为 fetch()
API 设计得很简单。
decorator 模式 是设计拦截 fetch()
请求的一个很好的解决方案。让我们试着使用它。
首先,定义一个有 doFetch()
方法的类 Fetcher()
(它只是简单地调用 fetch()
函数):
class Fetcher {
doFetch(resource, options) {
return fetch(resource, options);
}
}
然后让我们创建类 Fetcher
的实例,并使用它请求电影列表:
const fetcher = new Fetcher();
async function fetchMoviesBadStatus() {
const response = await fetcher.doFetch('/movies');
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
const movies = await response.json();
return movies;
}
fetchMoviesBadStatus()
.then((movies) => {
// When fetch succeeds
movies;
})
.catch((error) => {
// When fetch ends with a bad http status
error.message;
});
不直接调用 Fetch API,而是实例化 fetcher
类 const fetcher = new Fetcher()
,然后调用 await fetcher.doFetch('/movies')
开始请求。
if 语句 if (!response.ok) { ......}
里面的逻辑是:如果响应状态在 200
到 299
范围之内,就会抛出一个错误。这个逻辑应该重构成一个拦截器,因为它对响应进行修改。让我们把这个逻辑移到一个装饰器中,FetchDecoratorBadStatus
:
class FetchDecoratorBadStatus {
decoratee;
constructor(decoratee) {
this.decoratee = decoratee;
}
async doFetch(resource, options) {
const response = await this.decoratee.doFetch(resource, options);
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
return response;
}
}
FetchDecoratorBadStatus
封装了一个 Fetcher
实例(decoratee
属性)。decoratee
的 doFetch()
方法调用 this.decoratee.doFetch(resource, options)
,之后做出 if(!response.ok) { .... }
状态检查(拦截)。
由于将错误拦截逻辑移到 FetchDecoratorBadStatus
,fetchMoviesBadStatus()
可以被简化:
const fetcher = new FetchDecoratorBadStatus(new Fetcher());
async function fetchMoviesBadStatus() {
const response = await fetcher.doFetch('/movies');
const movies = await response.json();
return movies;
}
fetchMoviesBadStatus()
.then((movies) => {
// When fetch succeeds
// 当请求成功
movies;
})
.catch((error) => {
// When fetch ends with a bad HTTP status, e.g. 404
// 当请求结果是不好的的 HTTP 状态,例如 404
error.message;
});
new FetchDecoratorBadStatus(new Fetcher())
就是你装饰常规 Fetcher
实例的方式。因为装饰器并没有改变被装饰的 Fetcher
的接口,所以你可以像以前一样取电影列表:await fetcher.doFetch('/movies')
。
装饰器模式的设计好处是使 Fetcher
和 FetchDecoratorBadStatus
松散耦合。你可以轻松地添加或删除装饰器,而不影响使用 fetcher
实例的客户端代码:await fetcher.doFetch('/movies')
。
你可以用任意多的装饰符来包裹 fetcher
:
const fetcher = new FetchDecorator1(
new FetchDecorator2(new FetchDecoratorBadStatus(new Fetcher())),
);
总结
调用 fetch()
启动一个请求并返回一个 promise。当请求完成后,promise 会解析到响应对象。从响应对象中,你可以提取你需要的格式的数据:JSON, text, Blob.
因为 fetch()
返回的是一个 promise,所以你可以通过使用 async/await
语法来简化代码。
你已经知道了如何使用 fetch()
伴随着 async/await
来获取 JSON 数据,处理获取错误,取消请求,执行并发请求,以及如何使用装饰符拦截请求。