Markyan04
Markyan04
发布于 2025-11-14 / 18 阅读
1
0

如何解决请求依赖:从回调地狱到Async/Await(旧文迁移)

问题背景

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

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

axios.get('/api/authority').then(res => {
    axios.get('/api/timeTable', {
        params: { authority: authority.data }
    }).then (res => {
        // 处理时间表数据
    }).catch(err => {
        // 处理时间表请求错误
    })
}).catch(error => {
    // 处理权限请求错误
})

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

解决方案

Async/Await方案

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

async function loadData() {
    try {
        const authorityData = await axios.get('/api/authority')
        const spaces = await axios.get('/api/timeTable', {
            params: { authority: authorityData.data } 
        })
    } catch (error) {
        console.error('请求失败:,error)
    }
}

并行优化方案

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

async function loadAllData() {
    try {
        const [authorityData, otherData] = await Promise.all([
            axios.get('/api/authority'),
            axios.get('/api/otherApi')
        ])

        const spaces = await axios.get('/api/timeTable'), {
            params: { 
                authority: authorityData.data 
                other: otherData.data 
            }
        })
    } catch (error) {
        // 同一错误管理
    }
}

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

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

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

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

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

// 自动执行权限加载
const loadPermissions = async () => {
    try {
        const res = await axios.get('/api/authority')
        permissions.value = res.data
    } catch (error) {
        // 错误处理
    }
}

// 响应式监听
watch(authorityData, async(newVal)) => {
    if (!newVal) return
    try {
        const res = await await axios.get('/api/timeTable', {
            params: { authority: authorityData.data } 
        })
        space.value = res.data
    } catch (error) {
        // 错误处理
    }
}

onMounted(() => {
    loadPermissions()
})
</script>


评论