简体   繁体   English

如何使用 Axios 和 Vue 3 在组件级别优雅地处理响应中的错误?

[英]How to gracefully handle errors in responses on a component level with Axios and Vue 3?

I am sorry if it is obvious/well-covered elsewhere, but my google-fu has been failing me for over a full day by now.如果它在其他地方很明显/覆盖得很好,我很抱歉,但我的 google-fu 已经让我失望了一整天了。 What I would like to achieve is a rich component-level handling of request errors: toaster notifications, status bars, you name it.我想要实现的是丰富的组件级请求错误处理:烤面包机通知、状态栏,应有尽有。 The most obvious use case is auth guards/redirects, but there may be other scenarios as well (eg handling 500 status codes).最明显的用例是 auth guards/redirects,但也可能有其他场景(例如处理 500 状态代码)。 For now, app-wide interceptors would do, but there is an obvious (to me, at least) benefit in being able to supplement or override higher-level interceptors.现在,应用程序范围的拦截器就可以了,但是能够补充或覆盖更高级别的拦截器有一个明显的(至少对我而言)好处。 For example, if I have interceptors for 403 and 500 codes app-wide, I might want to override an interceptor for 403, but leave an interceptor for 500 intact on a component level.例如,如果我在应用程序范围内有用于 403 和 500 代码的拦截器,我可能想覆盖 403 的拦截器,但在组件级别上保留 500 的拦截器。

This would require access to component properties: I could then pass status messages in child components, create toaster notifications with custom timeouts/animations and so on.这将需要访问组件属性:然后我可以在子组件中传递状态消息,创建带有自定义超时/动画的烤面包机通知等等。 Naively, for my current app-wide problem, this functionality belongs in App.vue , but I can not figure out how to get access to App in axios.interceptors.response using the current plugin arrangement and whether it is okay to use a single axios instance app-wide in the first place.天真地,对于我当前的应用程序范围的问题,此功能属于App.vue ,但我无法弄清楚如何使用当前插件安排访问axios.interceptors.response中的App以及是否可以使用单个axios 应用程序范围内的第一个实例。

The trimmed down code I have tried so far (and which seems the most ubiquitous implementation found online) can be found below.到目前为止我尝试过的精简代码(这似乎是在网上找到的最普遍的实现)可以在下面找到。 It works with redirects, producing Error: getTranslators: detection is already running in the process (maybe because another 401 happens right after redirect with my current testing setup).它与重定向一起工作,产生Error: getTranslators: detection is already running in the process(可能是因为另一个 401 在我当前的测试设置重定向后立即发生)。 However, import Vue , both with curly brackets and without, fails miserably, and, more importantly, I have no way of accessing app properties and child components from the plugin.但是, import Vue ,无论是否带有大括号,都惨遭失败,而且更重要的是,我无法从插件访问应用程序属性和子组件。

// ./plugins/axios.js
import axios from 'axios';
import { globalStorage } from '@/store.js';
import router from '../router';
// Uncommenting this import gives Uncaught SyntaxError: ambiguous indirect export: default.
// Circular dependency?..
// import Vue from 'vue';

const api = axios.create({
  baseURL: import.meta.env.VUE_APP_API_URL,
});

api.interceptors.response.use(response => response,
error => {
 if (error.response.status === 401) {
   //Vue.$toast("Your session has expired. You will be redirected shortly");
   delete globalStorage.userInfo;
   localStorage.setItem('after_login', router.currentRoute.value.name);
   localStorage.removeItem('user_info');
   router.push({ name: 'login' });
 }
 return Promise.reject(error);
});

export default api;

// --------------------------------------------------------------------------

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import axios from './plugins/axios'
import VueAxios from 'vue-axios'

const app = createApp(App)

app.use(router)
  .use(VueAxios, axios)
  .mount('#app')

So, then, how do I get access to component properties in interceptors?那么,我如何访问拦截器中的组件属性呢? If I need them to behave differently for different components, would I then need multiple axios instances (assuming the behavior is not achieved by pure composition)?如果我需要它们针对不同的组件表现不同,那么我是否需要多个 axios 实例(假设行为不是通过纯组合实现的)? If so, where to put the relevant interceptor configuration and how to ensure some parts of global configuration such as baseURL apply to all of these instances?如果是这样,相关的拦截器配置放在哪里以及如何确保全局配置的某些部分(例如baseURL适用于所有这些实例?

I would prefer not having more major external dependencies such as Vuex as a complete replacement for the existing solution, but this is not a hill to die on, of course.我宁愿没有更多主要的外部依赖项,例如 Vuex 作为现有解决方案的完全替代品,但这当然不是一座山。

Instead of using axios's interceptors, you should probably create a composable .与其使用 axios 的拦截器,不如创建一个可组合的 . Consider the following:考虑以下:

composables/useApiRequest.js组合/useApiRequest.js

import axios from 'axios';
import { useToast } from "vue-toastification";

const useApiRequest = () => {
  const toast = useToast();

  const fetch = async (url) => {
    try {
      await axios.get(url);
    } catch (error) {
      if (error.response.status === 403) {
        toast.error("Your session has expired", {
          timeout: 2000
        });
      }
    }
  };

  return {
    fetch,
  };
};

export default useApiRequest;

Here we're creating a composable called useApiRequest that serves as our layer for the axios package where we can construct our api requests and create generic behaviors for certain response attributes.在这里,我们创建了一个名为useApiRequest的可组合项,用作 axios package 的层,我们可以在其中构建 api 请求并为某些响应属性创建通用行为。 Take note that we can safely use Vue's Composition API functions and also components such as the vue-toastification directly in this composable:请注意,我们可以安全地使用 Vue 的 Composition API 函数以及直接在此可组合项中使用vue-toastification等组件:

if (error.response.status === 403) {
  toast.error("Your session has expired", {
    timeout: 2000
  });
}

We can import this composable in the component and use the fetch function to send a GET request to whatever url that we supply:我们可以在组件中导入这个可组合项,并使用fetch function 向我们提供的任何 url 发送 GET 请求:

<script setup>
  import { ref } from 'vue';
  import useApiRequest from '../hooks/useApiRequest';

  const searchBar = ref('');
  const request = useApiRequest();

  const retrieveResult = async () => {
    await request.fetch(`https://api.ebird.org/v2/data/obs/${searchBar.value}/recent`);
  }
</script>

And that's it!就是这样! You can check the example here .您可以在此处查看示例。

Now, you mentioned that you want to access component properties.现在,您提到要访问组件属性。 You can accomplish this by letting your composable accept arguments containing the component properties:您可以通过让您的可组合项接受包含组件属性的 arguments 来完成此操作:

// `props` is our component props
const useApiRequest = (props) => {
  // add whatever logic you plan to implement for the props
}
<script setup>
  import { ref } from 'vue';
  import useApiRequest from '../hooks/useApiRequest';
  import { DEFAULT_STATUS } from '../constants';
 
  const status = ref(DEFAULT_STATUS);
  const request = useApiRequest({ status });
</script>

Just try to experiment and think of ways to make the composable more reusable for other components.只需尝试进行实验并想办法使可组合项对其他组件的可重用性更高。

Note笔记

I've updated the answer to change "hook" to "composable" as this is the correct term.我已经更新了将“钩子”更改为“可组合”的答案,因为这是正确的术语。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM