构建灵活的 Axios 客户端

发布日期:作者:

Building Flexible Axios Clients image

最近,我开始改进我在 Vue 应用程序中使用 API 的方式,通过构建一个灵活的 Axios 客户端,我可以在 Vuex actions 和一次性组件中使用它。

我更喜欢构建特定 API 的 JS 模块,我可以将它们导入到我的组件和 Vuex 模块中,而不是从组件中内置 Axios 调用。构建 API 资源模块使我能够抽象化使用 HTTP 资源,并为常见模式提供便利方法。让我们看看几个例子!

入门

我将定义一些我的 API 客户端通常需要满足的条件

  • 与错误报告挂钩(例如,Sentry)
  • 能够传递 HTTP 头部,例如 Authorization 头部
  • 能够在需要时利用我的 Vuex store(例如,获取用户的 JWT)
  • 资源特定方法,简化了常见 API 调用的方式(例如,users.find(userId)

我们编写的 API 客户端代码适用于您可能构建的任何 HTTP 客户端,但在本例中,我们将使用 Axios。

客户端

client.js 文件将是其他 HTTP 客户端用来创建新的 Axios 实例的基础模块。我们还将为请求和响应设置 Axios 所谓的“拦截器”。

请求拦截器接受一个请求配置和一个错误回调。您可以在此时自定义配置,但对于初学者,我们将返回相同的配置。错误回调将通过 Sentry 的 Raven 捕获异常

client.interceptors.request.use(
requestConfig => requestConfig,
(requestError) => {
Raven.captureException(requestError);
 
return Promise.reject(requestError);
},
);

响应拦截器接受两个参数,第一个参数是响应回调,第二个参数是响应错误

client.interceptors.response.use(
response => response,
(error) => {
if (error.response.status >= 500) {
Raven.captureException(error);
}
 
return Promise.reject(error);
},
);

以下是如何构建自己的基础 Axios 客户端的完整示例,该客户端利用 Sentry,检查 Vuex store 中是否有用户访问令牌,并提供一个 ApiClient 类,您可以使用它来构建自己的客户端。

import axios from 'axios';
import Raven from 'raven-js';
import store from '../store/index';
 
/**
* Create a new Axios client instance
* @see https://github.com/mzabriskie/axios#creating-an-instance
*/
const getClient = (baseUrl = null) => {
 
const options = {
baseURL: baseUrl
};
 
if (store.getters['users/isAuthenticated']) {
options.headers = {
Authorization: `Bearer ${store.getters['users/accessToken']}`,
};
}
 
const client = axios.create(options);
 
// Add a request interceptor
client.interceptors.request.use(
requestConfig => requestConfig,
(requestError) => {
Raven.captureException(requestError);
 
return Promise.reject(requestError);
},
);
 
// Add a response interceptor
client.interceptors.response.use(
response => response,
(error) => {
if (error.response.status >= 500) {
Raven.captureException(error);
}
 
return Promise.reject(error);
},
);
 
return client;
};
 
class ApiClient {
constructor(baseUrl = null) {
this.client = getClient(baseUrl);
}
 
get(url, conf = {}) {
return this.client.get(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
 
delete(url, conf = {}) {
return this.client.delete(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
 
head(url, conf = {}) {
return this.client.head(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
 
options(url, conf = {}) {
return this.client.options(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
 
post(url, data = {}, conf = {}) {
return this.client.post(url, data, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
 
put(url, data = {}, conf = {}) {
return this.client.put(url, data, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
 
patch(url, data = {}, conf = {}) {
return this.client.patch(url, data, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
}
 
export { ApiClient };
 
/**
* Base HTTP Client
*/
export default {
// Provide request methods with the default base_url
get(url, conf = {}) {
return getClient().get(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
 
delete(url, conf = {}) {
return getClient().delete(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
 
head(url, conf = {}) {
return getClient().head(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
 
options(url, conf = {}) {
return getClient().options(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
 
post(url, data = {}, conf = {}) {
return getClient().post(url, data, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
 
put(url, data = {}, conf = {}) {
return getClient().put(url, data, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
 
patch(url, data = {}, conf = {}) {
return getClient().patch(url, data, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
},
};

默认导出公开您可以直接使用 Axios 的所有 HTTP 方法。针对一次性请求的简单示例如下所示

import client from './client';
 
client.get('/users').then((response) => {
// do stuff
});

现在您拥有一个用于一次性请求的简单客户端,或者您可以将其用作构建更有趣的 HTTP 客户端的基础。

使用 client.js 创建 API 客户端

使用上面的客户端,您现在可以构建自己的 API 特定 Axios 客户端。

例如,假设您有一个 /users 资源,您可以构建特定的方法来管理用户

import { ApiClient } from '../client';
 
let client = new ApiClient();
 
export default {
 
all() {
return client.get('/users');
},
 
find(userId) {
return client.get(`/users/${userId}`);
},
 
update(userId, data) {
return client.put(`/users/${userId}`, data);
}
 
}

上面的示例很简单,但您可以在收到响应后通过链接 promise 调用来进行进一步处理。例如,您可能只发送响应中需要的相关属性。或者,您可能没有访问后端的权限,您可以使用响应构建自定义对象或添加额外的属性,例如,如果您只收到 firstNamelastName,那么可以添加一个 fullName 属性。Vue 也具有计算属性,但我希望您明白,您可以先格式化响应数据,然后再将其发送给消费者代码。

Vuex 用法

我建议您构建 HTTP 客户端模块的一个原因是,我们可以将它们拉到组件中,也可以在 Vuex store 中使用它们。相同的好处也适用,包括发送授权令牌以及避免直接在 Vuex 模块中进行 axios 调用。

以下是在 Vuex store 中获取用户并设置其个人资料数据的示例

import usersClient from './api/users';
 
// ...
 
profile({ commit }, userId) {
usersClient.find(userId)
.then((response) => {
let {
firstName,
lastName,
} = response.data;
 
commit('PROFILE', { firstName, lastName });
});
},

改进

我认为一个明显的改进是,在创建新客户端时,允许通过回调进行更灵活的客户端配置。我展示的使用 Vuex 发送 bearer 令牌的方式是以一种不太灵活的方式嵌入到模块中的。

我希望您至少能理解我想要传达的想法。构建一个基础客户端 JS 模块来处理您可能希望在任何 HTTP 客户端上使用的底层细节很有帮助。例如,我们可以专注于创建有用的 API 客户端来管理用户和其他资源,而不是担心将 Sentry 连接到我们的请求和响应。

您是否还有其他 Axios 或 JavaScript API 客户端技巧?在 Twitter 上告诉我们 @laravelnews.

Paul Redmond photo

Laravel 新闻的撰稿人。全栈 Web 开发人员和作者。

归档于
Cube

Laravel 新闻稿

加入 40,000 多名其他开发者,绝不错过新的技巧、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理服务器,并在几秒钟内部署 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

无妥协

Joel 和 Aaron,两位来自无妥协播客的经验丰富的开发者,现在可以为您的 Laravel 项目提供服务。 ⬧ 每月固定费用 7500 美元。 ⬧ 没有冗长的销售流程。 ⬧ 没有合同。 ⬧ 100% 退款保证。

无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

使用旧版 Laravel 版本?即时、自动化的 Laravel 升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

以每月 2500 美元的费用,为您的项目配备经验丰富的 Laravel 开发人员(拥有 4-6 年的经验)。获得 160 小时的专业知识和 15 天的无风险试用期。立即预约电话!

Bacancy
Lucky Media logo

Lucky Media

现在就变得幸运 - Laravel 开发的理想选择,拥有十多年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar:Laravel 电子商务

Laravel 电子商务。一个开源软件包,将现代无头电子商务功能的力量带到 Laravel。

Lunar:Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit:Laravel SaaS 启动工具包

SaaSykit 是一个 Laravel SaaS 启动工具包,它包含运行现代 SaaS 所需的所有功能。支付、漂亮的结账、管理面板、用户仪表板、身份验证、现成组件、统计数据、博客、文档等。

SaaSykit:Laravel SaaS 启动工具包
Rector logo

Rector

您的合作伙伴,可帮助您无缝升级 Laravel,降低成本,加速创新,帮助公司取得成功

Rector
MongoDB logo

MongoDB

通过将 MongoDB 和 Laravel 强大地集成到您的 PHP 应用程序中,赋能开发人员轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。了解 MongoDB 的灵活、现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的无依赖项输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速模糊网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为 Laravel 应用程序添加评论

阅读文章