《axios支持FormData与URLSearchParams数据传输》
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的请求和响应配置化,即用户可以配置`config`对象中的`headers`、`data`、`timeout`等属性来定制请求和响应。但是,在之前的实现中,我们允许用户配置的`data`属性只允许是一个普通对象,而实际上,`axios`的`post`请求的`data`属性不仅可以是普通对象,还可以是`URLSearchParams`对象、`FormData`对象等`buffer`对象,所以在这一篇文章中,我们就来支持用户可以配置`data`为`buffer`对象。
# 2. 需求分析
我们来看一下`axios`官网给出的使用`buffer`对象作为`data`的例子,如下:
```javascript
const FormData = require('form-data');
const form = new FormData();
form.append('my_field', 'my value');
form.append('my_buffer', new Buffer(10));
form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
axios.post('https://example.com', form);
```
从例子中可以看到,`data`可以是`FormData`对象,同理,也可以是`URLSearchParams`对象,如下:
```javascript
const params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);
```
那么,我们如何实现让用户配置`data`为`buffer`对象呢?
其实,我们只需要判断用户配置的`data`是不是`buffer`对象,如果是的话,我们就不需要再对`data`做任何处理了,因为`buffer`对象本身就可以直接用于`http`请求,并且我们还要把`headers`中的`Content-Type`设置为`buffer`对象对应的`Content-Type`,例如:`FormData`对象对应的`Content-Type`是`multipart/form-data`;`URLSearchParams`对象对应的`Content-Type`是`application/x-www-form-urlencoded;charset=utf-8`。
# 3. 判断 buffer 对象
那么,我们如何判断一个对象是不是`buffer`对象呢?我们可以使用以下方式来判断:
```javascript
function isBuffer(val) {
return (
val !== null &&
val !== undefined &&
val.constructor !== null &&
val.constructor !== undefined &&
typeof val.constructor.isBuffer === "function" &&
val.constructor.isBuffer(val)
);
}
```
但是,这种方式只能判断出`Buffer`对象,而判断不出`FormData`和`URLSearchParams`对象,所以我们需要换一种方式。
我们可以通过判断`val`上是否有`toString`方法并且方法的返回值为字符串,并且该字符串有特定标识来判断,例如:`FormData`对象的`toString`方法返回的字符串标识为`[object FormData]`;`URLSearchParams`对象的`toString`方法返回的字符串标识为`[object URLSearchParams]`。
所以,我们可以写一个`isBuffer`函数来判断:
```typescript
export function isBuffer(val: any): boolean {
return (
val !== null &&
val !== undefined &&
val.constructor !== null &&
val.constructor !== undefined &&
typeof val.constructor.isBuffer === "function" &&
val.constructor.isBuffer(val)
);
}
export function isFormData(val: any): boolean {
return typeof val !== "undefined" && val instanceof FormData;
}
export function isURLSearchParams(val: any): boolean {
return typeof val !== "undefined" && val instanceof URLSearchParams;
}
```
我们创建`src/helpers/util.ts`文件,将以上判断函数写入。
# 4. 处理 buffer 对象
如果用户配置的`data`是`buffer`对象,那么我们就不需要再对`data`做处理,并且还要把`headers`中的`Content-Type`设置为`buffer`对象对应的`Content-Type`。
我们在`src/core/dispatchRequest.ts`中的`transformRequest`函数里处理`data`的时候,先判断`data`是不是`FormData`、`ArrayBuffer`、`Buffer`、`Stream`、`File`、`Blob`等`buffer`对象,如果是的话,就直接返回`data`,并且还要设置对应的`Content-Type`。
我们在`src/core/dispatchRequest.ts`中引入判断函数:
```typescript
import { isFormData, isBuffer } from "../helpers/util";
```
然后在`transformRequest`函数中处理:
```typescript
function transformRequest(data: any, headers: any): any {
if (isFormData(data) || isBuffer(data)) {
return data;
}
// 省略其他代码
}
```
但是,我们还需要设置对应的`Content-Type`,我们在`src/core/dispatchRequest.ts`中写一个`setContentTypeIfUnset`函数:
```typescript
function setContentTypeIfUnset(headers: any, value: string): void {
if (!headers["Content-Type"]) {
headers["Content-Type"] = value;
}
}
```
然后在`transformRequest`函数中调用:
```typescript
function transformRequest(data: any, headers: any): any {
if (isFormData(data)) {
setContentTypeIfUnset(headers, "multipart/form-data;charset=utf-8");
return data;
}
if (isBuffer(data)) {
setContentTypeIfUnset(headers, "application/octet-stream");
return data;
}
// 省略其他代码
}
```
但是,我们还需要判断`URLSearchParams`对象,我们在`src/helpers/util.ts`中写一个`isURLSearchParams`函数:
```typescript
export function isURLSearchParams(val: any): boolean {
return typeof val !== "undefined" && val instanceof URLSearchParams;
}
```
然后在`src/core/dispatchRequest.ts`中引入:
```typescript
import { isFormData, isBuffer, isURLSearchParams } from "../helpers/util";
```
然后在`transformRequest`函数中处理:
```typescript
function transformRequest(data: any, headers: any): any {
if (isFormData(data)) {
setContentTypeIfUnset(headers, "multipart/form-data;charset=utf-8");
return data;
}
if (isBuffer(data)) {
setContentTypeIfUnset(headers, "application/octet-stream");
return data;
}
if (isURLSearchParams(data)) {
setContentTypeIfUnset(
headers,
"application/x-www-form-urlencoded;charset=utf-8"
);
return data;
}
// 省略其他代码
}
```
# 5. 测试
这样,我们就实现了让用户配置`data`为`buffer`对象的功能。我们写一个测试例子来测试一下。
在`examples/buffer`目录下创建`index.`:
```
< lang="en">
buffer example
>
```
在`examples/buffer`目录下创建`app.ts`:
```typescript
import axios from "../../src/index";
import { URLSearchParams } from "url";
const params = new URLSearchParams();
params.append("param1", "value1");
params.append("param2", "value2");
axios.post("/buffer/post", params);
```
在`server.js`中增加一个路由:
```javascript
router.post("/buffer/post", function(req, res) {
res.json(req.body);
});
```
然后在浏览器中打开`http://localhost:8000/buffer/`,查看网络请求,可以看到请求的`Content-Type`为`application/x-www-form-urlencoded;charset=utf-8`,并且请求的`data`为`param1=value1¶m2=value2`。

我们再来测试一下`FormData`对象,在`examples/buffer/app.ts`中增加:
```typescript
const formData = new FormData();
formData.append("name", "lwt");
formData.append("age", "18");
axios.post("/buffer/post", formData);
```
然后在浏览器中打开`http://localhost:8000/buffer/`,查看网络请求,可以看到请求的`Content-Type`为`multipart/form-data; boundary=----WebKitFormBoundaryxxx`,并且请求的`data`为`FormData`对象。

# 6. 遗留问题
我们虽然实现了让用户配置`data`为`buffer`对象的功能,但是我们还遗留了一个问题,就是当`data`是`FormData`对象的时候,我们设置的`Content-Type`是`multipart/form-data;charset=utf-8`,但是实际上,`FormData`对象的`Content-Type`应该是`multipart/form-data; boundary=----WebKitFormBoundaryxxx`,其中`boundary`是`FormData`对象自动生成的,我们不应该手动设置。
所以,我们需要判断,如果`data`是`FormData`对象,并且`headers`中已经设置了`Content-Type`,那么我们就不应该再设置`Content-Type`了。
我们在`src/core/dispatchRequest.ts`中的`setContentTypeIfUnset`函数中判断:
```typescript
function setContentTypeIfUnset(headers: any, value: string): void {
if (!headers["Content-Type"]) {
headers["Content-Type"] = value;
}
}
```
这样,如果`headers`中已经设置了`Content-Type`,我们就不会再次设置了。
# 7. 总结
在这篇文章中,我们实现了让用户配置`data`为`buffer`对象的功能,包括`FormData`对象、`URLSearchParams`对象等。我们通过判断`data`的类型,来设置对应的`Content-Type`,并且不对`data`做任何处理。
在下一篇文章中,我们将要实现`axios`的异常处理机制,包括网络错误、超时错误、状态码错误等。
最新文章
- 自动刹车系统提升汽车安全性能
- 汽车钣金变形登记证书:办理流程与重要性解析
- 汽车冷却系统维护保养指南
- 轮胎磨损影响汽车行驶安全定期检查
- 毫米波雷达:智能驾驶的核心传感器技术解析与应用
- 宝马全新电动SUV续航里程突破600公里
- 车辆严重损毁无法启动
- V2X通信技术:重塑未来智能交通与自动驾驶新生态
- 汽车三元催化器故障警示与保养全指南
- 智能仪表盘革命:从机械指针到全息投影的驾驶体验升级
- 电动化浪潮:锂离子电池与高压平台驱动汽车产业变革
- 固态电池革命:突破500Wh/kg能量密度的汽车产业新未来
- 汽车配气机构免赔额解析:气门与凸轮轴维修理赔指南
- V2X+高压快充+智能网联:三大技术重塑未来出行
- 汽车摄像头实时监控行车安全
- 自动驾驶技术发展:激光雷达与高精地图推动L3级突破
- 车路协同:让汽车与道路'对话'的未来交通革命
- 固态电池与自动驾驶引领未来:车联网技术重塑智慧交通新格局
- 车况检测保障行车安全
- 防腐蚀涂层提升汽车耐用性延长使用寿命
