你如何使用Axios拦截器?

172

我看过axios的文档,但它只是说:

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
  // Do something with response data
  return response;
}, function (error) {
  // Do something with response error
  return Promise.reject(error);
});

很多教程只展示这段代码,但我不清楚它的用途,可以有人给我一个简单的例子吗?

7个回答

244
简单来说,拦截器是每个HTTP动作的一个检查点。每个API调用都会经过这个拦截器。
那么,为什么需要两个拦截器呢?
一个API调用由请求和响应两部分组成。由于它像一个检查点一样运行,因此请求和响应具有分别的拦截器。
一些请求拦截器的用例 -
假设您想在发出请求之前检查您的凭据是否有效。因此,您可以在拦截器级别上检查您的凭据是否有效,而不是实际进行API调用。
假设您需要将令牌附加到每个请求中,而不是在每个Axios调用中重复令牌添加逻辑,您可以创建一个拦截器,在每个发出的请求上附加一个令牌。
一些响应拦截器的用例 -
假设您收到了一个响应,并根据API响应判断用户是否已登录。因此,在响应拦截器中,您可以初始化一个处理用户登录状态的类,并根据接收到的响应对象相应地更新它。
假设您使用有效的API凭据请求了一些API,但没有有效的角色访问数据。因此,您可以从响应拦截器触发一个警报,表示用户不被允许。这样,您将得到免于在每个Axios请求上执行未经授权的API错误处理的救赎。
以下是一些代码示例
请求拦截器
可以通过执行以下操作(在这种情况下,通过检查环境变量)打印axios的配置对象:
const DEBUG = process.env.NODE_ENV === "development";

axios.interceptors.request.use((config) => {
    /** In dev, intercepts request and logs it into console for dev */
    if (DEBUG) { console.info("✉️ ", config); }
    return config;
}, (error) => {
    if (DEBUG) { console.error("✉️ ", error); }
    return Promise.reject(error);
});
  • 如果想要检查正在传递的标头/添加任何通用标头,可以在config.headers对象中找到。例如:

  • axios.interceptors.request.use((config) => {
        config.headers.genericKey = "someGenericValue";
        return config;
    }, (error) => {
        return Promise.reject(error);
    });
    
  • 如果是一个GET请求,发送的查询参数可以在config.params对象中找到。

  • 响应拦截器

    • 您甚至可以在拦截器级别上可选地解析API响应并传递解析后的响应而不是原始响应。这可能会节省您多次编写解析逻辑的时间,以防在多个位置以相同方式使用API。一种方法是在api-request中传递一个额外的参数,并在响应拦截器中使用相同的参数执行您的操作。例如:

    //Assume we pass an extra parameter "parse: true" 
    axios.get("/city-list", { parse: true });
    

    一次,在响应拦截器中,我们可以像这样使用它:

    axios.interceptors.response.use((response) => {
        if (response.config.parse) {
            //perform the manipulation here and change the response object
        }
        return response;
    }, (error) => {
        return Promise.reject(error.message);
    });
    

    因此,在这种情况下,只要response.config中有一个parse对象,就会进行操作,对于其余情况,它将按原样工作。

  • 您甚至可以查看到达的HTTP代码,然后做出决定。例如:

  • axios.interceptors.response.use((response) => {
        if(response.status === 401) {
             alert("You are not authorized");
        }
        return response;
    }, (error) => {
        if (error.response && error.response.data) {
            return Promise.reject(error.response.data);
        }
        return Promise.reject(error.message);
    });
    

    2
    你把这些拦截器写在每个组件上还是写在整个应用的全局/中央位置? - James Poulose
    1
    @JamesPoulose 你可以将你的服务层分离出来,在该服务层的基础上添加拦截器,例如 services/index.js。这样前端应用程序就可以保持在一个地方,而服务则保持分离和平台无关 :D - Aseem Upadhyay
    3
    @AseemUpadhyay我将这段代码放在services/index.js中,然后export default axios,并在使用axios的任何地方import axios from "services/index.js"。但是我发现网上还有其他人创建axios的包装器...你个人使用哪种方法?谢谢 - Raj
    1
    @JamesPoulose 我通过在“src”文件夹下创建一个“services”文件夹解决了这个问题。在这个目录中,我会创建一个“request.js”或其他自定义名称来编写拦截器。 - nima
    如果我使用拦截器更改了baseURL,那么我该如何编写Jest单元测试来验证调用了哪个url?我已经模拟了axios,并在代码中传递了额外的url部分和拦截器更改了baseUrl以满足需要。 - sagar
    显示剩余9条评论

    45

    如果你想捕获从发送请求到接收响应所需的时间,可以使用这段代码:

    const axios = require("axios");
    
    (async () => {
      axios.interceptors.request.use(
        function (req) {
          req.time = { startTime: new Date() };
          return req;
        },
        (err) => {
          return Promise.reject(err);
        }
      );
    
      axios.interceptors.response.use(
        function (res) {
          res.config.time.endTime = new Date();
          res.duration =
            res.config.time.endTime - res.config.time.startTime;
          return res;
        },
        (err) => {
          return Promise.reject(err);
        }
      );
    
      axios
        .get("http://localhost:3000")
        .then((res) => {
          console.log(res.duration)
        })
        .catch((err) => {
          console.log(err);
        });
    })();
    

    7
    我给你的回复点赞是因为你在同一个地方展示了拦截器和实际调用。我之前很难将它们结合起来。 - Woodsman
    我也在寻找一个将两者结合起来的例子,它清楚地表明,如果你只想添加拦截器,那么你的原始调用不必改变。 - Qiniso

    23

    它就像一个中间件,基本上添加在任何请求(不管是GET、POST、PUT还是DELETE)或任何响应(从服务器得到的响应)上。

    它通常用于涉及授权的情况。

    看看这个链接:Axios拦截器与异步登录

    这里有另一篇关于此的文章,使用了不同的示例:https://medium.com/@danielalvidrez/handling-error-responses-with-grace-b6fd3c5886f0

    因此,其中一个示例的要点是,您可以使用拦截器来检测您的授权令牌是否过期(如果您例如得到403),并重定向页面。


    9

    我将给您更多实际使用案例,这些案例是我在真实项目中使用的。通常我使用请求拦截器来处理与令牌相关的事项(accessTokenrefreshToken),例如检查令牌是否过期,如果是,则使用 refreshToken 更新令牌并暂停所有其他调用,直到它解决为止。但我最喜欢的是 axios 的 响应拦截器,您可以在其中放置应用程序的全局错误处理逻辑,如下所示:

    httpClient.interceptors.response.use(
      (response: AxiosResponse) => {
        // Any status code that lie within the range of 2xx cause this function to trigger
        return response.data;
      },
      (err: AxiosError) => {
        // Any status codes that falls outside the range of 2xx cause this function to trigger
        const status = err.response?.status || 500;
        // we can handle global errors here
        switch (status) {
          // authentication (token related issues)
          case 401: {
            return Promise.reject(new APIError(err.message, 409));
          }
    
          // forbidden (permission related issues)
          case 403: {
            return Promise.reject(new APIError(err.message, 409));
          }
    
          // bad request
          case 400: {
            return Promise.reject(new APIError(err.message, 400));
          }
    
          // not found
          case 404: {
            return Promise.reject(new APIError(err.message, 404));
          }
    
          // conflict
          case 409: {
            return Promise.reject(new APIError(err.message, 409));
          }
    
          // unprocessable
          case 422: {
            return Promise.reject(new APIError(err.message, 422));
          }
    
          // generic api error (server related) unexpected
          default: {
            return Promise.reject(new APIError(err.message, 500));
          }
        }
      }
    );
    

    6
    这样怎么样,您可以创建一个新的 Axios 实例并将拦截器附加到它。然后您可以在应用程序中的任何地方使用该拦截器。
    export const axiosAuth = axios.create()
    
    //we intercept every requests 
    axiosAuth.interceptors.request.use(async function(config){
        //anything you want to attach to the requests such as token 
        return config;
    }, error => {
        return Promise.reject(error)
    })
    
    
    //we intercept every response
    axiosAuth.interceptors.response.use(async function(config){
        
        return config;
    }, error => {
    //check for authentication or anything like that
        return Promise.reject(error)
    })
    

    然后您可以像使用 axios 一样使用 axiosAuth


    1
    响应中的代码可能会有错误吗?这与请求是相同的吗? - romanown

    5

    这是我在项目中使用的方法。代码片段说明如何在axios拦截器中使用访问令牌和刷新令牌,并帮助实现刷新令牌功能。

    const API_URL =
        process.env.NODE_ENV === 'development'
            ? 'http://localhost:8080/admin/api'
            : '/admin-app/admin/api';
    
    const Service = axios.create({
        baseURL: API_URL,
        headers: {
            Accept: 'application/json',
        },
    });
    
    Service.interceptors.request.use(
        config => {
            const accessToken = localStorage.getItem('accessToken');
            if (accessToken) {
                config.headers.common = { Authorization: `Bearer ${accessToken}` };
            }
            return config;
        },
        error => {
            Promise.reject(error.response || error.message);
        }
    );
    
    Service.interceptors.response.use(
        response => {
            return response;
        },
        error => {
            let originalRequest = error.config;
            let refreshToken = localStorage.getItem('refreshToken');
            const username = EmailDecoder(); // decode email from jwt token subject
            if (
                refreshToken &&
                error.response.status === 403 &&
                !originalRequest._retry &&
                username
            ) {
                originalRequest._retry = true;
                return axios
                    .post(`${API_URL}/authentication/refresh`, {
                        refreshToken: refreshToken,
                        username,
                    })
                    .then(res => {
                        if (res.status === 200) {
                            localStorage.setItem(
                                'accessToken',
                                res.data.accessToken
                            );
                            localStorage.setItem(
                                'refreshToken',
                                res.data.refreshToken
                            );
    
                            originalRequest.headers[
                                'Authorization'
                            ] = `Bearer ${res.data.accessToken}`;
    
                            return axios(originalRequest);
                        }
                    })
                    .catch(() => {
                        localStorage.clear();
                        location.reload();
                    });
            }
            return Promise.reject(error.response || error.message);
        }
    );
    
    export default Service;


    4

    我已经按照以下方式实现了

    httpConfig.js

    import axios from 'axios'
    import { baseURL } from '../utils/config'
    import { SetupInterceptors } from './SetupInterceptors'
    
    
    const http = axios.create({
        baseURL: baseURL
    })
    
    SetupInterceptors(http)
    
    export default http
    

    SetupInterceptors.js

    import { baseURL } from '../utils/config'
    
    
    export const SetupInterceptors = http => {
    
        http.interceptors.request.use(
            config => {
                config.headers['token'] = `${localStorage.getItem('token')}`
                config.headers['content-type'] = 'application/json'
                return config
            },
            error => {
                return Promise.reject(error)
            }
        )
    
        http.interceptors.response.use(function(response) {
            return response
        }, function (error) {
            const status = error?.response?.status || 0
            const resBaseURL = error?.response?.config?.baseURL
            if (resBaseURL === baseURL && status === 401) {
                if (localStorage.getItem('token')) {
                    localStorage.clear()
                    window.location.assign('/')
                    return Promise.reject(error)
                } else {
                    return Promise.reject(error)
                }
            }
            return Promise.reject(error)
        })
    }
    
    export default SetupInterceptors
    

    参考资料:link


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接