使 fetch 更好,并使你的 API 请求方法更容易实现
在这篇文章中,我将分享我关于如何构建从 REST API 后端获取数据的方法的想法。我的想法是展示我的想法的基础知识,然后您可以根据自己的具体需求添加、删除和调整代码。
无论您使用什么框架或平台,例如 React、Angular、Vue 甚至 NodeJS(带有一些 polyfill,例如 fetch...)此方法都将非常有用!
哦,还有一点需要注意。我们接下来要学习面向对象编程。所以在继续学习之前,最好先对JavaScript 类和fetch api有基本的了解。
最终结果
最后,我们将能够像这样从后端请求数据来分配用户变量:
users = await backend.users.get()
而不是像这样:
const res = await fetch('/users', {
headers: {
Authorization: '********',
lang: 'en'
}
})
if(!res.ok) throw new Error(res.statusText)
users = await res.json()
动机
那么为什么要经历这个过程呢?首先,它会让你的代码更容易阅读。你会把 fetch 中的所有代码隐藏在解释性方法调用之后。这样既backend.get.users()
合理又简短。
当然,你可以将逻辑提取到一个名为 的函数中getUsers()
,并在其中进行 fetch 调用。但接下来还有另一个好处:不要重复自己。如果没有良好的后端请求结构,你肯定会重复自己。在多个地方设置授权和其他标头,或者只是为了看看 fetch 调用是否ok
无处不在……
您还可以轻松地将此代码移到外部库中,以便在您的 Web 应用程序和 Node 服务中使用。
让我们开始吧
因此,我们将首先制作我们自己的 Axios“迷你、迷你、迷你版本”(或在此处插入 http 客户端的名称):
class HttpClient {
constructor(options = {}) {
this._baseURL = options.baseURL || "";
this._headers = options.headers || {};
}
}
我们从构造函数开始,实例化类时我们将接受两个选项:
基础 URL将用于构建 URL。稍后我们将使用类似这样的 get 方法get('/users')
,如果我们定义了baseURL
请求https://jsonplaceholder.typicode.com
URL,则https://jsonplaceholder.typicode.com/users
标头将是随每次请求发送的一组默认标头。
如果你觉得“属性名前面的_是什么意思?”,其实我打算把这些属性设为private。也就是说,它们只能在这个类(或继承自这个类的类)内部使用,不能在实际应用代码之外的任何地方使用。很快也会有相应的语法来实现这个行为,可以看看这个。
我们可能还应该添加一些设置标题的方法:
setHeader(key, value) {
this._headers[key] = value;
return this;
}
我在 setHeader 方法的末尾添加了return this
。添加这个是为了方便我们进行chain
方法调用。例如,在实例化 HttpClient 类时:
const httpClient = new HttpClient({baseURL: 'xxx'})
.setBasicAuth("user", "pass")
.setHeader("lang", "en")
在上面的例子中,我使用了另一种方法setBasicAuth
。我现在先跳过这个方法,但在本文的最后,你会找到一些灵感,找到更多可以添加到客户端的属性和内容。
让我们提出请求!
这将分两步完成。首先,我们将为 fetch 定义自己的包装函数,然后我们将为 制定单独的方法get/post/put/delete/patch
:
async _fetchJSON(endpoint, options = {}) {
const res = await fetch(this._baseURL + endpoint, {
...options,
headers: this._headers
});
if (!res.ok) throw new Error(res.statusText);
if (options.parseResponse !== false && res.status !== 204)
return res.json();
return undefined;
}
所以这个包装函数只是让 fetch 的行为更符合我在这个特定用例中的期望。比如,fetch 不会在遇到错误请求时抛出异常。
第一个参数只是端点(字符串),如果我们设置baseURL
选项,它将与该选项相关。
options 参数就是Request
我们可以添加额外属性的 fetch 对象。它可以为空,但更多可用属性的信息可以在这里找到。
哦!不过我确实parseResponse
在这个参数后面附加了一些选项,用来指定是否应该将响应解析为 JSON。在大多数情况下,我都希望选择不进行解析。因此,如果留空,则表示解析已完成,除非 API 明确声明No Content
。
你可能会说我们可以检查内容长度或其他一些东西,但好处是,如果我确实需要响应,并且我说我希望它被解析。如果我没有得到响应,这个方法就会抛出异常。所以它会在这里崩溃,而不是在我的应用程序中崩溃,那样我可能需要更长的时间来找到原因。
现在让我们展示一些发出请求的方法。希望这些方法应该比较简单:
get(endpoint, options = {}) {
return this._fetchJSON(
endpoint,
{
...options,
method: 'GET'
}
)
}
post(endpoint, body, options = {}) {
return this._fetchJSON(
endpoint,
{
...options,
body: JSON.stringify(body),
method: 'POST'
}
)
}
delete(endpoint, options = {}) {
return this._fetchJSON(
endpoint,
{
parseResponse: false,
...options,
method: 'DELETE'
}
)
}
/** AND SO ON */
我们只需调用我们的_fetchJSON
方法并设置一些选项以使 HTTP 方法与我们的方法名称匹配,并可能设置一个正确的主体以便处理。
现在我们可以进行一些 API 调用:
const httpClient = new HttpClient({baseURL: 'https://example.com'})
.setHeader('lang', 'sv')
const users = await httpClient.get('/users')
更进一步:API 客户端
我们做了很多!这个客户端是我们自己的“迷你版” Axios。我们可以轻松地扩展它,添加任何我们需要的参数、选项或功能。
但我想更进一步,用易于调用的方法定义我们的后端 API。就像我在一开始提到的那样。现在我们可以采用两种方法之一。我们可以直接向 HttpClient 添加更多方法,然后继续工作。
但是,这个类现在确实达到了它的目的,对吧?它可以独立工作,并且很有用。那么,如果我们以 HttpClient 类为基类,并继承它来创建我们的 ApiClient 类,会怎么样呢?
这样,我们就可以直接使用 HttpClient 类来创建其他 HttpClient 来与其他服务通信。而使用 ApiClient 类与后端通信,这只是对现有功能的补充。
继承 HttpClient 类将如下所示:
import HttpClient from "./http-client"
class ApiClient extends HttpClient {
constructor(baseURL, langCode) {
super({
baseURL,
headers: {
lang: langCode
}
});
}
get users() {
return {
get: () => this.get("/users"),
delete: (id) => this.delete(`/users/${id}`),
create: (user) => this.post("/users", user),
update: (user) => this.put(`/users/${user.id}`, user)
};
}
}
export default ApiClient
嗯,速度相当快。我们只是在构造函数中添加了一些小细节,就能简单快速地定义端点了。
现在添加额外的端点真的非常简单和可靠。
更进一步
现在,这是一种快速添加基本功能然后扩展它以制作特定客户端的方法。
这里的想法是使基础尽可能简单,然后添加您需要的每个功能,而不是预先引入外部库的全部容量。
当然,如果适合您的需求,您可以接下来做一些事情:
如果不依赖 cookie,请添加助手进行身份验证
例如,如果您需要基本身份验证:
setBasicAuth(username, password) {
this._headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`
return this
}
请记住,btoa 在 NodeJS 中并非全局可用。但只需对其进行 polyfill 即可。
对于持有者授权:
setBearerAuth(token) {
this._headers.Authorization = `Bearer ${token}`
return this
}
如果你使用 TypeScript,请将函数设置为通用函数
我很喜欢 TypeScript,我 90% 的代码都是用 TypeScript 写的。在用 TypeScript 构建这个代码时,需要为函数添加一个泛型返回类型,并且对于你的 post 方法,你应该输入预期的主体部分:
_fetchJSON<T = any>(endpoint: string, options: RequestInit = {}):Promise<T>{ /**/ }
当主体未解析时,我通常会做一些小改动,return undefined as any
让 Typescript 不报错。如果你期望 undefinedT
应该出现undefined
,那么就可以了。
在您的 API 客户端中,对于这样的发布请求:
users = {
post: (user:IUser) => this.post<IUser>('/users', user)
}
将方法添加到您的 API 期望/可以使用的任何适用标头
例如,在工作中,我们有一个标题来在响应中包含或排除空值(以节省传输大量集合的时间)
includeEmptyAndDefault(shouldInclude) {
if(shouldInclude) {
this._headers.IncludeEmptyAndDefault = 1
} else {
this._headers.IncludeEmptyAndDefault = 0
}
return this
}
构建包
如果您使用 TypeScript,或者想要单独打包,可以使用Rollup或tsdx。这样,API 客户端也可以作为其他项目的模块使用。这对您和您的客户来说都非常有用,可以快速完成工作。
但正如我所说,只添加你需要的内容。如果你有任何想法,请在评论中分享,如果这不合你的口味,也请推荐一些你喜欢的图案给我。
文章来源:https://dev.to/stroemdev/make-fetch-better-and-your-api-request-methods-easier-to-implement-e9i封面照片由 Florian Krumm 在Unsplash上拍摄