MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue中网络请求的异步处理与错误捕获

2021-06-056.9k 阅读

Vue 中的网络请求基础

在 Vue 项目开发中,与后端进行数据交互是非常常见的需求。通常我们会使用网络请求来获取服务器端的数据或者向服务器发送数据。在 Vue 中,常用的网络请求库有 axios,它是一个基于 Promise 的 HTTP 客户端,可在浏览器和 Node.js 中使用。

首先,我们需要安装 axios。在项目根目录下执行以下命令:

npm install axios --save

安装完成后,在 Vue 组件中引入 axios 并使用它来发送请求。例如,发送一个 GET 请求获取数据:

<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: []
    };
  },
  mounted() {
    axios.get('https://example.com/api/data')
     .then(response => {
        this.dataList = response.data;
      })
     .catch(error => {
        console.error('请求出错', error);
      });
  }
};
</script>

在上述代码中,我们在组件的 mounted 钩子函数中使用 axios.get 方法发送一个 GET 请求到指定的 API 地址。如果请求成功,then 回调函数会被执行,我们将响应数据赋值给 dataList。如果请求失败,catch 回调函数会被执行,我们在控制台打印错误信息。

异步处理的本质

网络请求是异步操作,这意味着当我们发起一个网络请求时,JavaScript 不会等待请求完成后再继续执行后续代码,而是会立即执行请求代码之后的语句。这是因为网络请求的响应时间是不确定的,可能会因为网络延迟等原因花费较长时间。

JavaScript 实现异步操作主要通过回调函数、Promise 和 async/await。在 Vue 中使用 axios 时,我们主要依赖于 Promise 来处理异步操作。

Promise 基础

Promise 是一个表示异步操作最终完成(或失败)及其结果值的对象。它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。一旦 Promise 的状态从 pending 变为 fulfilledrejected,就不能再改变。

一个 Promise 对象通过 then 方法注册回调函数来处理成功或失败的结果。then 方法接收两个回调函数作为参数,第一个回调函数处理成功的结果(resolved),第二个回调函数处理失败的结果(rejected)。例如:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    Math.random() > 0.5? resolve('成功') : reject('失败');
  }, 1000);
});

promise
 .then(value => {
    console.log('成功:', value);
  })
 .catch(error => {
    console.log('失败:', error);
  });

在上述代码中,我们创建了一个 Promise 对象,它在 1 秒后根据随机数决定是 resolve 还是 reject。然后通过 thencatch 方法分别处理成功和失败的情况。

在 Vue 网络请求中使用 Promise

回到我们之前的 Vue 组件示例,axios 的请求方法(如 getpost 等)返回的都是一个 Promise 对象。所以我们可以链式调用 then 方法来处理响应。例如,我们可以在获取数据后再进行一些额外的处理:

<template>
  <div>
    <ul>
      <li v-for="item in filteredDataList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: [],
      filteredDataList: []
    };
  },
  mounted() {
    axios.get('https://example.com/api/data')
     .then(response => {
        this.dataList = response.data;
        return this.dataList.filter(item => item.age > 18);
      })
     .then(filteredData => {
        this.filteredDataList = filteredData;
      })
     .catch(error => {
        console.error('请求出错', error);
      });
  }
};
</script>

在这个示例中,我们首先获取数据并赋值给 dataList,然后通过 then 方法继续对数据进行过滤,将过滤后的数据赋值给 filteredDataList。如果请求过程中出现错误,catch 方法会捕获并处理错误。

async/await 在 Vue 网络请求中的应用

async/await 是基于 Promise 的语法糖,它使得异步代码看起来更像同步代码,提高了代码的可读性。async 关键字用于定义一个异步函数,该函数始终返回一个 Promise。await 关键字只能在 async 函数内部使用,它用于暂停异步函数的执行,直到 Promise 被解决(resolved 或 rejected)。

使用 async/await 进行网络请求

以下是使用 async/await 重写前面的 Vue 组件示例:

<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: []
    };
  },
  async mounted() {
    try {
      const response = await axios.get('https://example.com/api/data');
      this.dataList = response.data;
    } catch (error) {
      console.error('请求出错', error);
    }
  }
};
</script>

在上述代码中,我们将 mounted 钩子函数定义为 async 函数。在函数内部,使用 await 等待 axios.get 请求的 Promise 被解决。如果请求成功,await 会返回已解决的 Promise 的值(即响应数据),我们将其赋值给 dataList。如果请求失败,await 会抛出错误,我们通过 try...catch 块捕获并处理错误。

async/await 与链式调用的对比

与链式调用 then 方法相比,async/await 使得代码结构更清晰,更接近同步代码的写法。例如,在进行多个异步操作且后一个操作依赖前一个操作的结果时,链式调用 then 方法可能会导致代码嵌套过深,形成所谓的 “回调地狱”。而 async/await 可以避免这种情况。

假设我们需要先获取用户列表,然后根据用户 ID 获取每个用户的详细信息:

<template>
  <div>
    <ul>
      <li v-for="user in userDetailsList" :key="user.id">
        <p>{{ user.name }}</p>
        <p>{{ user.address }}</p>
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      userDetailsList: []
    };
  },
  async mounted() {
    try {
      const usersResponse = await axios.get('https://example.com/api/users');
      const userIds = usersResponse.data.map(user => user.id);
      const userDetailsPromises = userIds.map(id => axios.get(`https://example.com/api/users/${id}/details`));
      const userDetailsResponses = await Promise.all(userDetailsPromises);
      this.userDetailsList = userDetailsResponses.map(response => response.data);
    } catch (error) {
      console.error('请求出错', error);
    }
  }
};
</script>

在上述代码中,我们首先获取用户列表,然后提取用户 ID 并为每个用户 ID 发起获取详细信息的请求。通过 Promise.all 方法并行处理这些请求,await 等待所有请求完成后再将结果赋值给 userDetailsList。如果使用链式调用 then 方法,代码会变得更加复杂和难以阅读。

网络请求的错误捕获

在进行网络请求时,错误可能会在多个阶段发生,包括网络连接问题、服务器响应状态码错误、请求超时等。正确捕获和处理这些错误对于提高应用的稳定性和用户体验非常重要。

捕获 axios 请求错误

axios 通过 catch 方法来捕获请求过程中的错误。在前面的示例中,我们已经看到了基本的错误捕获方式:

<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: []
    };
  },
  mounted() {
    axios.get('https://example.com/api/data')
     .then(response => {
        this.dataList = response.data;
      })
     .catch(error => {
        console.error('请求出错', error);
      });
  }
};
</script>

当请求失败时,catch 回调函数会被执行,error 参数包含了错误信息。axios 的错误对象包含了一些有用的属性,例如 error.message 可以获取错误描述,error.response 可以获取服务器返回的响应(如果有)。

不同类型错误的处理

  1. 网络连接错误:当网络不可用或者请求的服务器地址不存在时,会发生网络连接错误。此时 error.message 通常会包含类似 “Network Error” 的信息。我们可以在 catch 回调中根据这个信息给用户友好的提示,例如:
<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
    <div v-if="networkError">网络连接出现问题,请检查网络设置。</div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: [],
      networkError: false
    };
  },
  mounted() {
    axios.get('https://example.com/api/data')
     .then(response => {
        this.dataList = response.data;
      })
     .catch(error => {
        if (error.message.includes('Network Error')) {
          this.networkError = true;
        }
        console.error('请求出错', error);
      });
  }
};
</script>
  1. 服务器响应状态码错误:如果服务器返回的状态码不是 2xx(表示成功),axios 也会将其视为错误。我们可以通过 error.response.status 获取状态码,并根据不同的状态码进行相应的处理。例如,当状态码为 404 时,提示用户资源未找到:
<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
    <div v-if="notFoundError">请求的资源未找到。</div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: [],
      notFoundError: false
    };
  },
  mounted() {
    axios.get('https://example.com/api/data')
     .then(response => {
        this.dataList = response.data;
      })
     .catch(error => {
        if (error.response && error.response.status === 404) {
          this.notFoundError = true;
        }
        console.error('请求出错', error);
      });
  }
};
</script>
  1. 请求超时错误axios 支持设置请求超时时间。当请求超过设定的时间仍未得到响应时,会抛出超时错误。我们可以在请求配置中设置 timeout 属性,并在 catch 回调中处理超时错误:
<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
    <div v-if="timeoutError">请求超时,请稍后重试。</div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      dataList: [],
      timeoutError: false
    };
  },
  mounted() {
    axios.get('https://example.com/api/data', {
      timeout: 5000 // 设置超时时间为 5 秒
    })
     .then(response => {
        this.dataList = response.data;
      })
     .catch(error => {
        if (error.message.includes('timeout')) {
          this.timeoutError = true;
        }
        console.error('请求出错', error);
      });
  }
};
</script>

在 Vuex 中处理网络请求的异步操作与错误捕获

Vuex 是 Vue.js 应用程序的状态管理模式。在大型项目中,通常会在 Vuex 中处理网络请求,以便更好地管理应用的状态。

在 Vuex 中使用 actions 进行网络请求

actions 是 Vuex 中处理异步操作的地方。例如,我们在 Vuex 的 store.js 文件中定义一个获取数据的 action

import axios from 'axios';

const state = {
  dataList: []
};

const mutations = {
  SET_DATA_LIST(state, data) {
    state.dataList = data;
  }
};

const actions = {
  async fetchData({ commit }) {
    try {
      const response = await axios.get('https://example.com/api/data');
      commit('SET_DATA_LIST', response.data);
    } catch (error) {
      console.error('请求出错', error);
    }
  }
};

export default {
  state,
  mutations,
  actions
};

在上述代码中,我们定义了一个 fetchDataaction,它使用 async/await 进行网络请求。如果请求成功,通过 commit 触发 SET_DATA_LIST mutation 来更新 state 中的 dataList。如果请求失败,捕获并打印错误信息。

在组件中调用 Vuex 的 actions 并处理错误

在 Vue 组件中,我们可以通过 this.$store.dispatch 方法来调用 Vuex 的 actions。例如:

<template>
  <div>
    <ul>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
    <div v-if="fetchError">获取数据出错,请稍后重试。</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dataList: [],
      fetchError: false
    };
  },
  async mounted() {
    try {
      await this.$store.dispatch('fetchData');
      this.dataList = this.$store.state.dataList;
    } catch (error) {
      this.fetchError = true;
    }
  }
};
</script>

在这个组件中,我们在 mounted 钩子函数中调用 fetchData action。如果 dispatch 返回的 Promise 被解决(即请求成功),我们从 Vuex 的 state 中获取数据并赋值给组件的 dataList。如果 Promise 被拒绝(即请求失败),我们设置 fetchErrortrue 以显示错误提示。

全局错误处理

在 Vue 应用中,我们可以设置全局的错误处理机制,以便统一处理所有网络请求以及其他可能出现的错误。

使用 Vue.config.errorHandler

Vue 提供了 Vue.config.errorHandler 配置项来全局捕获 Vue 组件渲染、事件处理或 async 函数中抛出的未捕获错误。我们可以在 main.js 文件中设置:

import Vue from 'vue';
import App from './App.vue';
import axios from 'axios';

Vue.config.errorHandler = (error, vm, info) => {
  if (error.message.includes('Network Error')) {
    console.error('全局捕获到网络连接错误');
  } else if (error.response && error.response.status === 404) {
    console.error('全局捕获到资源未找到错误');
  } else {
    console.error('全局捕获到错误', error, '错误信息', info);
  }
};

Vue.prototype.$axios = axios;

new Vue({
  render: h => h(App)
}).$mount('#app');

在上述代码中,我们定义了 Vue.config.errorHandler 函数,它接收三个参数:error(错误对象)、vm(发生错误的 Vue 实例)和 info(错误信息,例如错误发生在哪个生命周期钩子函数中)。我们可以根据错误信息进行不同的处理,例如记录错误日志、给用户友好的提示等。

在全局 axios 拦截器中处理错误

axios 提供了拦截器功能,我们可以在请求或响应被 thencatch 处理前拦截它们。通过设置全局的 axios 拦截器,我们可以统一处理所有 axios 请求的错误。例如:

import axios from 'axios';

axios.interceptors.response.use(
  response => {
    return response;
  },
  error => {
    if (error.message.includes('Network Error')) {
      console.error('全局 axios 拦截器捕获到网络连接错误');
    } else if (error.response && error.response.status === 404) {
      console.error('全局 axios 拦截器捕获到资源未找到错误');
    } else {
      console.error('全局 axios 拦截器捕获到错误', error);
    }
    return Promise.reject(error);
  }
);

在上述代码中,我们使用 axios.interceptors.response.use 方法设置了响应拦截器。在拦截器的 error 回调中,我们对不同类型的错误进行处理,并最终通过 Promise.reject(error) 将错误继续传递,以便在具体的请求处也能进行处理。

通过以上全面的介绍,我们深入了解了 Vue 中网络请求的异步处理与错误捕获的各个方面,从基础的请求方式到异步处理的本质,再到各种错误的捕获与处理,以及在 Vuex 和全局层面的应用,这些知识对于构建健壮、稳定的 Vue 应用至关重要。