《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`的异常处理机制,包括网络错误、超时错误、状态码错误等。
最新文章
- 发动机过热导致汽车无法正常行驶
- 行驶证如何影响车险保额?核定载人数等关键因素解析
- 智能副驾ADAS:你的车如何用科技守护安全?
- 智能驾驶革命:车联网与新能源重塑未来出行新生态
- 智能液晶屏革新车险服务:车损险显示技术解析与未来展望
- ECU优化指南:提升动力10%-30%,降低油耗5%-15%
- 激光雷达与车联网技术如何重塑未来汽车安全格局
- 自动驾驶技术发展现状、挑战与未来趋势分析
- 宝马全新M4雷霆版震撼上市
- 电动化浪潮与智能驾驶崛起:车联网时代的汽车产业变革
- V2X与激光雷达:车联网如何重塑未来智能驾驶体验
- 汽车防垢涂层技术提升驾驶安全
- 汽车驾驶安全与技巧
- 汽车清洁保养小技巧分享
- 2023汽车科技革命:电动化、自动驾驶与5G车联网新突破
- 电动化、智能化、车联网:汽车产业三大变革趋势解析
- 特斯拉电动跑车加速性能卓越续航里程长
- 汽车改装备案指南:轮毂、车身颜色变更流程解析
- 方向盘拨片控制发动机转速的驾驶技巧与安全指南
- 硅基负极:突破动力电池续航瓶颈的关键技术
