question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

using try/catch with an async method inside a middleware causes a `nuxt instance unavailable` error.

See original GitHub issue

Environment


  • Operating System: Linux
  • Node Version: v17.9.0
  • Nuxt Version: 3.0.0-rc.4
  • Package Manager: npm@8.5.5
  • Builder: vite
  • User Config: modules, runtimeConfig, autoImports
  • Runtime Modules: @nuxtjs/tailwindcss@5.1.3
  • Build Modules: -

Reproduction

Visit the /secret page to see the error:

https://stackblitz.com/edit/nuxt-starter-elpq43?file=middleware%2Fauth.ts

Describe the bug

I’m trying to write an async middleware. However, when catching the rejected promise an error is thrown by nuxt when using navigateTo():

async function fetchUser() {
  throw new Error();
}

export default defineNuxtRouteMiddleware(async (to, from) => {
  let user;

  try {
    user = await fetchUser();
  } catch (e) {
    user = null;
  }

  if (!user) return navigateTo('/');
});

Additional context

No response

Logs

No response

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:2
  • Comments:9 (7 by maintainers)

github_iconTop GitHub Comments

14reactions
pi0commented, Aug 29, 2022

Hi! Sorry this issue was left unanswered for so long.

Short story: Calling navigateTo in an async context should work out of the box when using defineNuxtPlugin() and defineNuxtRouteMiddleware() wrappers but it is not working when using an await inside try/catch. (Please see the next section below for full story)

Here is a quick solution: (Update: upstream issue fixed. You probably don’t need this workaround anymore! Just update the lockfile.)

  • Keep an instance of nuxtApp using useNuxtApp before any async call
  • Call navigateTo via callWithNuxt utility from #app.
import { callWithNuxt } from '#app'

export default defineNuxtRouteMiddleware(async (to, from) => {
  const nuxtApp = useNuxtApp()
  try {
    user = await fetchUser()
  } catch (e) {
    user = null
  }
  if (!user) {
    return callWithNuxt(nuxtApp, navigateTo, ['/auth'])
  }
})

Updated sandbox: https://stackblitz.com/edit/nuxt-starter-u5esgu?file=middleware%2Fauth.ts,app.vue

And the story…

Let me explain what happens when using navigateTo inside an async function and after async/await and how composables work.

The way Vue.js composition API (and Nuxt composables similarly) work is 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.

What it does means? Composition API and Nuxt Composables are only available during lifecycle and in same tick before any async operation:

// --- vue internal ---
const _vueInstance = null
const getCurrentInstance = () => _vueInstance
// ---

// Vue / Nuxt sets a global variable referencing to current component in _vueInstance when calling setup()
async setup() {
  getCurrentInstance() // Works
  await someAsyncOperation() // Vue unsets the context in same tick before async operation!
  getCurrentInstance() // null
}

The classic solution to this, is caching the current instance on first call to a local variable like const instance = getCurrentInstance() and use it in next composables but the issue is that any nested composable calls now needs to explicitly accept the instance as an argument and not depend on magical implicit context of composition-api. This is design limitation with composables and not an issue per-se.

To overcome this limitation, Vue does some dark magic when compiling our application code and restores context after each call for <script setup>:

const __instance = getCurrentInstance() // Generated by vue compiler
getCurrentInstance() // Works!
await someAsyncOperation() // Vue unsets the context
__restoreInstance(__instance) // Generated by vue compiler
getCurrentInstance() // Still works!

For a better description of what Vue actually does, see https://github.com/unjs/unctx/issues/2#issuecomment-942193723.

In Nuxt, we have an (internal) utility callWithNuxt utility that we can use to to restore context similar to how <script setup> works which i used for solution above.

Nuxt 3 internally use unjs/unctx to support composables similar to vue for plugins and middleware. This enabled us to make composables like navigateTo() to work without directly passing nuxtApp to them. This brings in all DX and Performance (of tree-shaking) benefits Vue Composition has to the whole Nuxt framework.

With Nuxt composables, we have the same design of Vue Composition API therefore needed a similar solution to magically do this transform. Check out https://github.com/unjs/unctx/issues/2 (Proposal), https://github.com/unjs/unctx/pull/4 (Transform implementation), and https://github.com/nuxt/framework/pull/3884 (Integration to Nuxt).

Vue currently only supports async context restoration for <script setup> for async/await usage. For Nuxt, we additionally added magic transform for defineNuxtPlugin() and defineNuxtRouteMiddleware()! This means when you use them, Nuxt automatically transforms them with context restoration.

The…Bug…: The unctx transformation to automatically restore context seems buggy with try/catch statements containing await which we have to solve in order to remove the requirement of the workaround I suggested above.

2reactions
antfucommented, Aug 29, 2022
Read more comments on GitHub >

github_iconTop Results From Across the Web

Express middleware cannot trap errors thrown by async/await ...
Here, the error will get trapped by try/catch: router. get('/force_async_error/0', async function (req, res, next) { try{ await Promise. reject ...
Read more >
Consolidating Error Handling in Nuxt Apps - Zaengle Corp
Here's how we previously handled Axios errors in our Nuxt apps, ... A method in a component async handleSubmit() { try { await...
Read more >
Getting Started With Axios In Nuxt - Smashing Magazine
In this tutorial, we will learn how to make a request in our Nuxt.js ... Note that asyncData method can only be used...
Read more >
Handling errors in Durable Functions - Azure | Microsoft Learn
Learn how to handle errors in the Durable Functions extension for Azure Functions.
Read more >
Error handling in NuxtJS - Damir's Corner
You can log the unhandled errors in server error middleware but you can't handle them there to prevent the static server error page...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found