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