实现 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全栈工程师。