实现 Axios 同步请求:兼容 XHR 的队列式解决方案
在我们开发 vue
应用项目时,axios
是一个非常好用的 HTTP
请求库,我们可以用它来发送 HTTP
请求,但是在某些情况下,我们需要将使用 axios
的所有 HTTP
请求变为同步请求,例如在适配某些项目时,后端接口是对 token
进行单线加密验证的场景。
为了 api
库能够在多个模块中使用,便于统一修改,我们可以封装一下 axios
的请求。
在适配某些其他项目时,其他项目也有可能发送 XHR
请求,但我们又无法管控和修改其他项目的代码,所以我们要获取当前是否有 XHR
请求,如果有,则需要排队等待上一个请求响应结束后才会去发送下一个请求。
这里我们需要一个 XHR
检测工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 if (!window .XMLHttpRequest .__isPatched ) { const OriginalXHR = window .XMLHttpRequest ; if (!Object .prototype .hasOwnProperty .call (window , "WA_activeXHRCount" )) { window .WA_activeXHRCount = 0 ; } window .XMLHttpRequest = function ( ) { const xhr = new OriginalXHR (); const originalSend = xhr.send .bind (xhr); xhr.send = function (...args ) { window .WA_activeXHRCount ++; originalSend (...args); }; const decrement = ( ) => { window .WA_activeXHRCount --; }; xhr.addEventListener ("load" , decrement); xhr.addEventListener ("error" , decrement); xhr.addEventListener ("abort" , decrement); xhr.addEventListener ("timeout" , decrement); return xhr; }; window .XMLHttpRequest .__isPatched = true ; } window .getActiveXHRCount = () => window .WA_activeXHRCount ;
接下来我们要封装 axios
请求,达到所有使用该库发送请求的 api
请求都变成同步请求的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 import axios from "axios" ;import api from "../utils/api" ;import "./xhr-detect" ;const isObject = (obj ) => { return obj !== null && typeof obj === "object" && !Array .isArray (obj); }; const isFunIn = (fun ) => { const name = fun.name ; return Object .keys (api).indexOf (name) > -1 ; }; let defaultConfig = { baseURL : "/" + process.env .VUE_APP_API_PRE , timeout : 1000 * 15 , withCredentials : true , headers : { "Content-Type" : "application/x-www-form-urlencoded" , "X-Requested-With" : "XMLHttpRequest" , }, }; const service = axios.create ();if (!Object .prototype .hasOwnProperty .call (window , "WA_requestQueue" )) { window .WA_requestQueue = []; } if (!Object .prototype .hasOwnProperty .call (window , "WA_requestQueue" )) { window .WA_isRequesting = false ; } const processQueue = ( ) => { if (window .WA_isRequesting || window .WA_requestQueue .length === 0 ) { return ; } if (window .getActiveXHRCount () > 0 ) { setTimeout (() => { processQueue (); }); return ; } window .WA_isRequesting = true ; const { config, fun, resolve, reject } = window .WA_requestQueue .shift (); let options; if (isObject (config) && fun === null ) { options = { ...config }; } else if (Array .isArray (config) && typeof fun === "function" && isFunIn (fun)) { options = fun (...config); } else { console .error ("request参数错误,请检查api配置" ); return ; } options = Object .assign (defaultConfig, options); service (options) .then ((response ) => { resolve (response); }) .catch ((error ) => { reject (error); }) .finally (() => { window .WA_isRequesting = false ; processQueue (); }); }; const request = (config = {}, fun = null ) => { return new Promise ((resolve, reject ) => { window .WA_requestQueue .push ({ config, fun, resolve, reject }); processQueue (); }); }; const setDefaultOption = (option ) => { defaultConfig = Object .assign (defaultConfig, option); }; export default { request : request, service : service, setDefaultOption : setDefaultOption };
提示
上面的有个 api
库,是对外暴露了一些处理方法,因为后端特定设计的原因,需要我们对传递的数据进行动态加密,而且不同的相请求,处理方法不一样,例如文件上传、命令调用等,需要有不同的处理方法,所以,我们把处理方法封装到 api
中,然后通过 api
暴露给外部使用,这样,我们的代码就清晰了很多。所以 api
一个函数集合,在上面的代码中也有用于判断函数是否在预计中的逻辑。
我们需要将该模块库中的所有的处理方法及 axios
库一起对外暴露
1 2 3 4 import api from "./utils/api" ;import axios from "./axios/index" ;export default { api : api, axios : axios };
将上面的库构建后,可以在具体的应用项目中进行使用。
这样我们就实现了将 axios
的所有请求变为同步请求,而且不影响 axios
的请求与响应的拦截器的定义,在定义 api
中可以像下面这样使用。
首先,在我们的项目中需要定义一个统一的 api
处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import api from "@webape/api" ;const service = api.axios .service ;api.axios .setDefaultOption ({ baseURL : "/" + process.env .VUE_APP_API_PRE , timeout : 10000 , withCredentials : true , headers : { "Content-Type" : "application/x-www-form-urlencoded" , "X-Requested-With" : "XMLHttpRequest" , }, }); service.interceptors .request .use ( (config ) => { return config; }, (error ) => { return Promise .reject (error); } ); service.interceptors .response .use ( (response ) => { const { data } = response; if (Object .prototype .toString .call (data) === "[object Array]" ) { return Promise .resolve (data); } else { return Promise .reject ("数据结构异常" ); } }, (error ) => { if (error.message .includes ("timeout" )) { return Promise .reject ("请求超时,请稍后再试" ); } else if (error.message .includes ("NetworkError" )) { return Promise .reject ("网络错误,请稍后再试" ); } else { return Promise .reject (error); } } ); export default api.axios .request ;
然后在应用项目的 api
定义文件中像下面这样使用:
1 2 3 4 5 6 7 8 9 10 11 import request from "@/utils/request" ;import api from "@webape/api" ;export function show (data ) { return request (["commandXml函数参数param1" , "commandXml函数参数param2" , "commandXml函数参数param3" ], api.api .commandXml ); } export function show2 (data ) { return request ({ url : "/webape.net/api/commandXml" , method : "post" , data : { webape : "webape" } }); }
然后就可以在具体的 vue
文件中 import
后进行使用了,这样只要使用封装的 axios
库的请求,都会被处理为同步请求,而且兼容在同一个项目中,有其他不被管控制的 XHR
请求,也会等待串行处理。
致力于网站建设与Web开发。喜欢新事物,关注前后端动态,对新的技术有追求, 做一个优秀的web全栈工程师。