当前位置 : 首页 > 购买指南

Axios多环境适配:浏览器与Node.js双平台请求实现

<|begin▁of▁sentence|># 1. 前言 在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只是实现了一个很简单的`axios`,一个最最基础的`axios`,到目前为止,我们只实现了: - 在`浏览器端`使用`XMLHttpRequest`对象通信 - 支持`Promise`API - 支持请求和响应数据配置 - 支持请求和响应错误处理 但是,我们还有很多功能没有实现,比如: - 支持`node.js`端发送请求 - 支持请求拦截器 - 支持响应拦截器 - 支持取消请求 - 等等 从这篇文章开始,我们就来把这些功能一一实现。 # 2. 需求分析 在之前的文章中,我们只实现了在`浏览器端`发送请求,这是因为`axios` 是一个基于 `Promise` 的 `HTTP` 请求库,可以用在浏览器和 `node.js` 中。它优秀的特性如:支持`Promise`API、拦截请求和响应、转换请求和响应数据、自动转换`JSON`数据、客户端支持防御`XSRF`等。所以,我们也要让我们的`axios`能够同时支持在浏览器端和`node.js`端发送请求。 那么,如何让我们的`axios`能够同时支持在浏览器端和`node.js`端发送请求呢?我们首先想到的肯定是要判断当前运行环境,如果是浏览器环境,我们就使用`XMLHttpRequest`对象实现发送请求;如果是`node.js`环境,我们就使用`node.js`内置的`http`模块实现发送请求。 所以,接下来,我们就先来实现让`axios`能够支持在`node.js`端发送请求。 # 3. 运行环境判断 我们想要判断当前运行环境,首先就要能够获取到当前运行环境,那么如何获取呢? 在浏览器环境中,全局对象是`window`,并且`window`对象上有`document`对象,但是在`node.js`环境中,全局对象是`global`,并且没有`document`对象,所以我们可以根据这一点来判断当前运行环境。 我们可以在`src/axios.ts`文件中添加一个辅助函数`getDefaultAdapter`来获取默认的请求发送装置: ```typescript import { AxiosRequestConfig } from "./types"; import xhr from "./xhr"; function getDefaultAdapter(): any { if (typeof XMLHttpRequest !== "undefined") { // 浏览器环境 return xhr; } } ``` 然后,在`axios`函数中,我们先获取默认的请求发送装置,然后使用该装置发送请求: ```typescript function axios(config: AxiosRequestConfig): AxiosPromise { const adapter = getDefaultAdapter(); return adapter(config); } ``` OK,这样我们就实现了在浏览器环境中使用`xhr`装置发送请求,接下来,我们就来实现`node.js`环境中的请求发送装置。 # 4. 实现 node.js 端请求发送装置 在`node.js`环境中,我们使用`http`模块实现请求发送,我们在`src`目录下创建`adapter`目录,并在该目录下创建`http.ts`文件,在该文件中实现`node.js`端的请求发送装置。 ## 4.1 创建 http.ts `http.ts`文件内容如下: ```typescript import { AxiosPromise, AxiosRequestConfig } from "../types"; import { buildURL } from "../helpers/url"; import { transformRequest } from "../helpers/data"; import { processHeaders } from "../helpers/headers"; export default function http(config: AxiosRequestConfig): AxiosPromise { return new Promise((resolve, reject) => { const { url, method = "get", data = null, params, headers, responseType, } = config; const { http, https } = require("follow-redirects"); // 处理url let fullUrl = buildURL(url, params); // 处理请求头 let newHeaders = processHeaders(headers, data); // 处理数据 let newData = transformRequest(data); // 创建请求对象 const transport = fullUrl.startsWith("https") ? https : http; // 创建请求 const req = transport.request( fullUrl, { method: method.toUpperCase(), headers: newHeaders, }, (res: any) => { if (res.statusCode >= 200 && res.statusCode < 300) { let responseData = null; if (responseType === "text") { responseData = res.read().toString(); } else { responseData = res.read(); } const response = { data: responseData, status: res.statusCode, statusText: res.statusMessage, headers: res.headers, config, request: req, }; resolve(response); } else { reject( new Error(`Request failed with status code ${res.statusCode}`) ); } } ); // 处理错误 req.on("error", (err: any) => { reject(err); }); // 处理超时 req.setTimeout(0); // 发送请求 if (newData) { req.write(newData); } req.end(); }); } ``` 代码说明: - 首先,我们从`config`中解构出我们需要的属性,并且给`method`和`data`设置默认值; - 然后,我们引入`node.js`中的`http`和`https`模块,这里我们使用第三方库`follow-redirects`,它可以自动重定向,并且同时支持`http`和`https`; - 接着,我们处理`url`,将`params`参数拼接到`url`上; - 然后,我们处理请求头,将`headers`中的属性名转换成首字母大写的形式; - 然后,我们处理请求数据,将`data`转换成`JSON`字符串; - 然后,我们根据`url`的协议来判断使用`http`还是`https`模块; - 然后,我们创建请求,并监听响应事件,当响应状态码在 200 到 300 之间时,我们认为请求成功,将响应数据转换成字符串或者`Buffer`,并返回响应对象;否则,我们认为请求失败,返回错误信息; - 然后,我们监听错误事件,当发生错误时,返回错误信息; - 然后,我们设置超时时间为 0,表示不超时; - 最后,我们发送请求,如果有请求数据,我们就将数据写入请求体,然后结束请求。 ## 4.2 修改 getDefaultAdapter 函数 现在,我们已经实现了`node.js`端的请求发送装置,接下来,我们修改`getDefaultAdapter`函数,让它能够返回`node.js`端的请求发送装置。 ```typescript import { AxiosRequestConfig } from "./types"; import xhr from "./xhr"; import http from "./adapter/http"; function getDefaultAdapter(): any { if (typeof XMLHttpRequest !== "undefined") { // 浏览器环境 return xhr; } else if (typeof process !== "undefined") { // node.js环境 return http; } } ``` 我们通过判断全局对象`process`是否存在来判断当前是否是`node.js`环境,如果存在,就返回`http`装置。 ## 4.3 安装 follow-redirects 由于我们在`http.ts`中使用了第三方库`follow-redirects`,所以我们需要先安装它: ```bash npm install follow-redirects --save ``` 安装完成之后,我们就可以在`node.js`环境中使用`http`装置发送请求了。 # 5. 编写 demo 接下来,我们编写一个`demo`来测试一下我们的`axios`是否能够在`node.js`环境中发送请求。 我们在`examples`目录下创建`server.js`文件,在该文件中我们创建一个简单的`http`服务器,用来接收请求并返回响应。 `server.js`文件内容如下: ```javascript const http = require("http"); const server = http.createServer((req, res) => { res.writeHead(200, { "Content-Type": "application/json" }); res.end( JSON.stringify({ data: "Hello World!", }) ); }); server.listen(3000, () => { console.log("Server is running on http://localhost:3000"); }); ``` 然后,我们在`examples`目录下创建`node.js`文件,在该文件中我们使用`axios`发送请求。 `node.js`文件内容如下: ```javascript const axios = require("../../src/index"); axios({ url: "http://localhost:3000", method: "get", }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); ``` # 6. 运行 demo 首先,我们启动服务器: ```bash node examples/server.js ``` 然后,我们运行`node.js`文件: ```bash node examples/node.js ``` 运行之后,我们可以在控制台看到如下输出: ![](~@/axios/05/01.png) OK,我们可以看到,我们的`axios`已经能够在`node.js`环境中发送请求了,并且成功收到了响应。 # 7. 遗留问题 我们虽然已经实现了在`node.js`环境中发送请求,但是我们还存在一些问题: - 我们没有处理`responseType`,在`node.js`环境中,我们只能处理`text`和`json`,不能处理`arraybuffer`和`blob`等; - 我们没有处理`cancelToken`,在`node.js`环境中,我们无法取消请求; - 我们没有处理`withCredentials`,在`node.js`环境中,我们无法设置`withCredentials`; - 我们没有处理`auth`,在`node.js`环境中,我们无法设置`auth`; - 我们没有处理`proxy`,在`node.js`环境中,我们无法设置`proxy`; - 等等。 这些问题,我们会在后面的文章中一一解决。 # 8. 总结 在本篇文章中,我们让`axios`能够同时支持在浏览器端和`node.js`端发送请求。我们通过判断当前运行环境来选择使用不同的请求发送装置。在浏览器环境中,我们使用`XMLHttpRequest`对象发送请求;在`node.js`环境中,我们使用`http`模块发送请求。 至此,我们的`axios`已经能够在浏览器和`node.js`环境中发送请求了,但是我们还存在很多问题,这些问题我们会在后面的文章中一一解决。

栏目列表