背景:顶层 await
Async 异步函数能将 Promise 的链式调用的形式,改为同步的形式,对于编写和阅读代码都非常友好。但一直以来都有一个限制,就是 async 和 await 这两个关键字必须成对出现。这就导致了一个问题,想使用 await,必须要将其定义在一个 async 函数中,再调用此函数。所以 ECMAScript 中一直有一个提案,叫做顶层 await,它 支持在 async 函数以外使用 await,但是只能在一个模块的顶层中使用。这个提案于2022年正式成为 ES 标准语法。
过去的写法是这样的:
async function fn() {
const value = await 10; // await 不仅可以接Promise,也可以将普通值
console.log(value)
}
main();
使用顶层await 之后,就可以这样写了:
const value = await 10;
console.log(value)
但是一定有一个前提,必须处于模块的顶层。我就是没有注意这一点,导致出现了一个问题。
bug 出现
有一次开发时突发奇想,想用一下这个顶层await。于是写下了类似下面的代码:
<script setup>
const res = await axios(url)
</script>
然后过了一会,等去调试页面时才发现已经白屏了。于是打开调试工具,在元素面板中看到了整个页面的路由都没有渲染出来,只剩下一个空注释节点了:
在控制台中看到了如下警告:
[Vue warn]: Component <Anonymous>: setup function returned a promise, but no <Suspense> boundary was found in the parent component tree. A component with async setup() must be nested in a <Suspense> in order to be rendered.
起初按照直觉,以为是路由表配置出了问题,导致路由渲染不出来,反复调试路由配置,始终不奏效。然后搜索这条警告信息,果然有不少朋友都遇到过,无一例外是在 <script setup>
中使用了顶层 await。那我就纳闷了,说好的顶层await怎么不好用了呢?
盯着代码想了一会,突然想起 <script setup>
只是语法糖啊!那么上面的代码就相当于在 setup 方法中使用了 await:
<script>
export default {
setup() {
const res = await axios(url)
}
}
</script>
这自然不是在模块顶层中使用了,也就导致了在解析上出现问题,导致组件不能正确渲染,最终导致对应的路由视图没有渲染出来。
小结
起初由于空页面,空节点,误解是路由的问题。其实 <router-view>
之所以被渲染成注释节点,看似是路由组件没有正确渲染,也有可能是组件本身出了问题。另外,在使用一些新特性要特别注意使用条件。