Markyan04
Markyan04
发布于 2025-04-20 / 23 阅读
0
0

【开发杂谈2】如何解决请求依赖:从回调地狱到Async/Await

问题背景:

我们设想这样一个场景,现在我们需要实现一个用户查看可预约空间的功能,用户可以预约哪些空间受限于用户的权限。现在有两个接口,/api/timeTable为获取空间时间表的函数,/api/authority为获取用户权限信息的函数。那么显然,前者是依赖于后者的,请求作为异步任务,我们是无法保证其顺序执行的。一种很直观的解决方案就是,“诶,我有个点子,直接将前者放到后者的成功回调不就好了?”

然而,这样简单粗暴的解决方式的结果就是“回调地狱”。

// 当前业务逻辑的案例
axios.get('/api/authority').then(res => {
  axios.get('/api/timeTable', {
    params: { authority: authority.data }
  }).then(res => {
    // 处理时间表数据
  }).catch(error => {
    // 处理时间表请求错误
  })
}).catch(error => {
  // 处理权限请求错误
})

在这个场景中,我们姑且还只有两层嵌套结构,但是这显然不是一个好的开端。随着业务逻辑的扩展,代码将会开始向右无限延申,形成一种谁看谁想跑路的金字塔结构,不利于维护,也不利于团队协同开发。倘若在如此长的回调链条中,某一回调触发错误的同时未进行错误处理,将会导致静默失败。那么,我们应该如何解决这种困境呢?

解决方案:

Async/Await方案

Async/Await是ES7所引入的新特性,其是基于Promise实现的语法糖,其使得异步代码的书写更接近于同步代码。其中async用于声明异步函数,await用于暂停代码的执行,等待Promise返回的结果。这样的解决方案一方面集中化处理的异常,同时也避免了宛如金字塔一般的地狱嵌套结构。

async function loadData() {
  try {
    const permissions = await axios.get('/api/authority')
    const spaces = await axios.get('/api/timeTable', {
      params: { authority: authority.data }
    })
    // 处理数据逻辑
  } catch (error) {
    console.error('请求失败:', error)
  }
}

并行优化方案

对于多条独立请求,可以通过Promise.all并发执行无依赖关系的请求,减少整体等待时间(从t1 + t2变为max(t1, t2)),然后继续结合上述提到的Async/Await解决方案,混合串行 + 并行解决方案,可以兼顾效率和代码可读性。

async function loadAllData() {
  try {
    const [permissionsRes, otherDataRes] = await Promise.all([
      axios.get('/api/authority'),
      axios.get('/api/timeTable')
    ])
    
    const spacesRes = await axios.get('/api/spaces', {
      params: { authority: authority.data }
    })
    
    return {
      permissions: permissionsRes.data,
      spaces: spacesRes.data,
      other: otherDataRes.data
    }
  } catch (error) {
    // 统一错误处理
  }
}

响应式解决方案 - Vue watch监听器

上述的两个解决方案都是对任意前端开发框架通用的,不过如果采用Vue开发,则可以考虑充分利用Vue的响应式特性来解决这个问题(不过对于上述简单场景中,个人认为这种解决方案过于繁琐了)。

将权限加载和空间加载分离为独立函数,通过 Vue 的响应式系统自动触发依赖更新。当 permissions 变化时,watch 会自动执行空间加载逻辑。同时,当依赖关系出现动态变化时,watch监视器则拥有良好的适配性,而Async/Await方案则需要重新编写逻辑(当然还可以和Pinia/Vuex这种状态管理库结合,实现跨组件通信)。

<script setup>
import { ref, watch } from 'vue'

const permissions = ref(null)
const spaces = ref([])

// 自动执行权限加载
const loadPermissions = async () => {
  try {
    const res = await getPermissions()
    permissions.value = res.data
  } catch (error) {
    console.error('权限加载失败:', error)
  }
}

// 响应式监听
watch(permissions, async (newVal) => {
  if (!newVal) return
  try {
    const res = await getSpaces({ permissions: newVal })
    spaces.value = res.data
  } catch (error) {
    console.error('空间加载失败:', error)
  }
})

onMounted(() => {
  // 初始化加载
  loadPermissions()
})
</script>


评论