解决nuxt3提示nuxt instance unavailable 错误
2025-01-11 nuxtjs 问题定位 nuxt3 827 字 2 分钟
问题描述
最近产品提了一个问题,有一个页面在生产环境下500错误,必现,但是在本地开发环境下没有问题,以及测试环境都没有复现该问题。
问题分析
-
本地开发环境没有问题,说明大概率是数据问题导致,前端代码出现未捕获的异常导致的(node项目做了DCDN会缓存页面, 所以异常的页面要返回标准状态码,而不是自定义状态页)。
-
本地连接生产环境的接口,复现问题, 该页面存在兜底逻辑, 响应数据为空,去掉一部分条件进行第一次重试, 如果响应还是空,那么就只保留一个条件第二次重试。
-
通过日志报错信息,发现是nuxt instance unavailable
错误, 说明是nuxt实例不可用。查看整个错误堆栈信息,调用链如下,
getRecommendData -> useAsyncData -> useNuxtApp -> nuxt instance unavailable
也就是在第二次尝试的时候,调用了useNuxtApp
方法, 但是nuxt实例不可用。通过查询nuxt3 官网得知。
A Deeper Explanation of Context
Vue.js Composition API (and Nuxt composables similarly) work by depending on an implicit context.
During the lifecycle, Vue sets the temporary instance of the current component
(and Nuxt temporary instance of nuxtApp) to a global variable and unsets it in same tick.
When rendering on the server side, there are multiple requests from different users and nuxtApp
running in a same global context. Because of this, Nuxt and Vue immediately unset this global
instance to avoid leaking a shared reference between two users or components.
在Composition API
中上下文是隐性传递的,Vue在组件的生命周期中设置当前组件的临时实例(nuxtApp的临时实例)到全局变量中,并在同一个时刻取消设置。
而且在服务端渲染的时候,有多个用户请求,nuxtApp 运行在同一个全局上下文中,因此nuxt和vue会立即取消设置全局实例,以避免在两个用户或组件之间泄漏共享引用。
所以问题就出现在这里了,第二次重试的时候,nuxt实例已经被取消了,所以useNuxtApp
获取不到,会报错nuxt instance unavailable
。
解决方案
官方提供的方案是显示的传递nuxt实例vue 3.3.2+不推荐
async function getRecommendData(bool: boolean) {
const nuxtApp = useNuxtApp()
// do something with nuxtApp
const { code, data } = nuxtApp.runWithContext(useAsyncData(() => pageQueryEnterpriseDatabase())
if(data?.length) {
recommendData.value = data
}
if(bool && !data || !data.length) {
getRecommendData(false)
}
}
社区提供的方案不推荐
import { callWithNuxt } from 'nuxt/app'
async function getRecommendData(bool: boolean) {
const nuxtApp = useNuxtApp()
// do something with nuxtApp
const { code, data } = await callWithNuxt(nuxtApp, useAsyncData(() => pageQueryEnterpriseDatabase()))
if(data?.length) {
recommendData.value = data
}
if(bool && !data || !data.length) {
getRecommendData(false)
}
}
官方建议的方案推荐
import { pageQueryEnterpriseDatabase } from '@/api'
import { callWithNuxt } from 'nuxt/app'
async function getRecommendData(bool: boolean) {
const { code, data } = await pageQueryEnterpriseDatabase()
if(data?.length) {
return data
}
if(bool && !data || !data.length) {
return await getRecommendData(false)
}
return []
}
const { data: recommendData } = useAsyncData(() => {
getRecommendData()
})
问题总结
vue 本身不建议使用顶层await, 导致await之后的代码无法获取实例上下文, 本质原因是setup本身是个同步执行的函数,内部在执行完毕后会清空实例数据。实验性质中会讲setup转换为async setup
,同时对于使用await时会使用withAsyncContext
对其进行包裹来重新获取上下文。
vue3 官方文档
async setup() 必须与 Suspense 组合使用,该特性目前仍处于实验阶段。我们计划在未来的版本中完成该特性并编写文档——但如果你现在就感兴趣,可以参考其测试来了解其工作方式。
nuxt中本质是是同一个问题导致的,nuxt3中提供了runWithContext
方法,可以在异步函数中获取nuxt实例上下文。但是也是不推荐使用的。
最好的方法是将await放到生命周期函数中,或者使用useAsyncData
来获取数据,这样就不会出现nuxt实例不可用的问题。如本次的问题,useAsyncData
本身就是支持异步函数,而不单纯的像useFetch
一样作为请求用的。所以我们重试的逻辑应该是放大一个异步函数中完成,并最终返回结果数据。
参考资料