Nuxt3: useFetch使用过程常见一种报错

一、问题描述

先看一段代码:

<script setup>
const fetchData = async () => {
  const { data, error } = await useFetch('https://api.publicapis.org/entries');
  const { data: data2, error: error2 } = await useFetch(
    'https://api.publicapis.org/entries'
  );
};

await fetchData(); // if you remove await the app will start, but server terminal will return same error
</script>

<template>
  <div>
    <NuxtWelcome />
  </div>
</template>

这段代码在不同的Nuxt版本的报错会有不同,但本质是一样的问题:

Nuxt 3.1.1:

nuxt instance unavailable

在这里插入图片描述
Nuxt 3.10.3:

[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.

在这里插入图片描述

也可以直接访问https://stackblitz.com/edit/github-qe9ulj-opndzv?file=app.vue,在线运行代码,查看结果。

这个问题,在之前的文章《Nuxt: A composable that requires access to the Nuxt instance was called outside of a plugin…》有提到过,这次针对useFetch的使用再次遇到该问题,就再花点时间进行记录。

之所以关注到该问题,是因为对useFetch封装后,在页面setup中调用时(以某种特定的方式进行),出现了上述的错误。

为了更好的描述问题,先简单看一段能正常运行的代码:

<script setup>
const fetchData = async () => {
  const { data, error } = await useFetch('https://api.publicapis.org/entries');
};

await fetchData(); // if you remove await the app will start, but server terminal will return same error
</script>

<template>
  <div>
    <NuxtWelcome />
  </div>
</template>

这段代码与之前的相比,唯一差别就是async fetchData 内只出现了一次await useFetch的调用,但它却能正常运行:

在这里插入图片描述
一旦async fetchData 内多了一次await useFetch调用,就直接报错了:

在这里插入图片描述
但如果再换另一种方式就又正常了:

<script setup>
const fetchData = async () => {
  const res = await Promise.all([
    useFetch('https://api.publicapis.org/entries'),
    useFetch('https://api.publicapis.org/entries'),
  ]);
};

await fetchData(); // if you remove await the app will start, but server terminal will return same error
</script>

<template>
  <div>
    <NuxtWelcome />
  </div>
</template>

在这里插入图片描述
而一旦在await Promise.all之后再添加useFetch的调用就立马又报错:
在这里插入图片描述

这个问题在此看似乎可以描述为:在方法内多次调用useFetch,会导致错误
正好有一篇《Nuxt 3 useFetch - nuxt instance unavailable when using useFetch at least twice in one function》提到了这样的观点,之前的代码也是来源于此。但它的标题描述的不够准确,因为不是在方法内多次调用useFetch就一定会有问题,比如:
在这里插入图片描述
上面这样就很正常,那么出问题的地方就在于是否使用了await。为了验证这个想法,先只给第一个useFetch加上await

在这里插入图片描述
接下来,只给第二个useFetch加上await
在这里插入图片描述

至此,该问题可以描述为:在方法内一旦出现了await useFetch之后,再有useFetch的调用就会报错。

二、问题调查

前面问题描述了比较长的过程,而关于此问题也有人提到:

After calling useFetch within a function the context is lost - it should be called either directly within your setup block (in which case the context will be preserved - or you can use callWithNuxt. More info here: #14269 (comment).

#14269 里最后提到nuxtApp.runWithContext #23258

根据这些,可以猜测:应该是因为在useFetch在不恰当调用环境下,nuxt的context丢失,导致useFetch报错。

我们找到runWithContext的文档看看:

You are likely here because you got a “Nuxt instance unavailable” message. Please use this method sparingly, and report examples that are causing issues, so that it can ultimately be solved at the framework level.

The runWithContext method is meant to be used to call a function and give it an explicit Nuxt context. Typically, the Nuxt context is passed around implicitly and you do not need to worry about this. However, when working with complex async/await scenarios in middleware/plugins, you can run into instances where the current instance has been unset after an async call.

该runWithContext方法旨在用于调用函数并为其提供显式 Nuxt 上下文。通常,Nuxt 上下文会隐式传递,您无需担心这一点。但是,在处理中间件/插件中的复杂async/await场景时,您可能会遇到异步调用后当前实例已取消设置的情况。

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.

Vue.js Composition API(以及类似的 Nuxt 组合函数)通过依赖隐式上下文来工作。在生命周期中,Vue 将当前组件的临时实例(以及 nuxtApp 的 Nuxt 临时实例)设置为全局变量,并在同一Tick阶段销毁。在服务器端渲染时,有来自不同用户的多个请求,并且 nuxtApp 在同一全局上下文中运行。因此,Nuxt 和 Vue 立即取消设置此全局实例,以避免泄漏两个用户或组件之间的共享引用。

里面提到in same tick,这里的tick应该是跟Node Tick类似:

event loop 的每次迭代,在nodejs 中就叫做 “Tick” 。
在Node.js中,事件循环是一个持续运行的过程,负责处理事件和执行回调函数。事件循环包含了不同的阶段,其中之一就是"tick"阶段。在每个"tick"阶段,Node.js会执行以下几个主要任务:

  1. 执行微任务(microtasks):在每个"tick"阶段开始时,Node.js会首先执行所有微任务队列中的任务。微任务通常包括Promise回调、process.nextTick()等。
  2. 执行定时器检查:Node.js会检查定时器队列,查看是否有定时器到期需要执行。如果有定时器到期,Node.js会将其回调函数放入事件队列中,等待下一个"tick"阶段执行。
  3. 执行IO操作:Node.js会处理已经完成的IO操作的回调函数。这包括文件读写、网络请求等异步操作的回调函数。
  4. 执行事件回调:Node.js会执行事件队列中的事件回调函数。这些事件可能是由网络请求、定时器、IO操作等触发的。
  5. 检查是否需要继续下一个"tick"阶段:在当前"tick"阶段执行完毕后,Node.js会检查是否需要继续下一个"tick"阶段。如果事件队列中还有待处理的事件,Node.js会继续执行下一个"tick"阶段。

通过这样的"tick"阶段循环,Node.js能够高效地处理异步操作和事件回调,保证应用程序的响应性和性能。

再结合有关Vue and Nuxt composables的文档介绍

Vue and Nuxt composables
When you are using the built-in Composition API composables provided by Vue and Nuxt, be aware that many of them rely on being called in the right context.
During a component lifecycle, Vue tracks the temporary instance of the current component (and similarly, Nuxt tracks a temporary instance of nuxtApp) via a global variable, and then unsets it in same tick. This is essential when server rendering, both to avoid cross-request state pollution (leaking a shared reference between two users) and to avoid leakage between different components.
That means that (with very few exceptions) you cannot use them outside a Nuxt plugin, Nuxt route middleware or Vue setup function. On top of that, you must use them synchronously - that is, you cannot use await before calling a composable, except within

当您使用 Vue 和 Nuxt 提供的内置 Composition API 组合函数时,请注意它们中的许多都依赖于在正确的上下文中调用。
在组件生命周期中,Vue 通过全局变量跟踪当前组件的临时实例(类似地,Nuxt 跟踪nuxtApp的临时实例),然后在同一Tick阶段销毁。这在服务器渲染时至关重要,既可以避免交叉请求状态污染(泄漏两个用户之间的共享引用),又可以避免不同组件之间的泄漏。
这意味着(除了极少数例外)你不能在 Nuxt 插件、Nuxt 路由中间件或 Vue 设置函数之外使用它们。最重要的是,您必须同步使用它们 - 也就是说,您不能在组合函数前使用await关键字,除非在<script setup>块内,在使用以defineNuxtComponent方式声明的组件的setup函数内,在defineNuxtPlugin或者defineNuxtRouteMiddleware中,这些地方即使在await后我们会执行一个转换以保持同步上下文。

也就是说,在<script setup>内,直接调用useFetch,即使useFetch前面使用await关键字也能正常访问到Nuxt Context,所以这种情况下它都能正常运行,这也是你为什么看到的useFetch的使用示例大多如此的原因:

在这里插入图片描述

通过上述介绍,现在可以知道之前描述的种种问题产生的原因,是因为在useFetch前使用await关键字,会导致它们处于不同的Tick阶段,而无法访问Nuxt Context引起报错。

  • 14
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓风伴月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值