问题背景:
我们设想这样一个场景,现在我们需要实现一个用户查看可预约空间的功能,用户可以预约哪些空间受限于用户的权限。现在有两个接口,/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>