
如何与 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) => {