当前位置 : 首页 > 保养数据

《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`。 ![](~@/axios/15/01.png) 我们再来测试一下`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`对象。 ![](~@/axios/15/02.png) # 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`的异常处理机制,包括网络错误、超时错误、状态码错误等。

栏目列表