如何与 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 数据,处理获取错误,取消请求,执行并发请求,以及如何使用装饰符拦截请求。
本文由 吳文俊 翻译,原文地址 How to Use Fetch with async/await