Repository: nuxt/nuxt
File: packages/nuxt/src/app/composables/asyncData.ts (TypeScript)

Code before:

        if (options.immediate || hasRun || isRunning) {
          nuxtApp._asyncData[newKey].execute(initialFetchOptions)
        }
      } else {
        asyncData._execute({ cause: 'watch', dedupe: options.dedupe })
      }
    }, { flush: 'sync' })

    if (hasScope) {
      onScopeDispose(() => {
        unsubExecute()
        unregister(key.value)
      })
    }
  }

  const asyncReturn: _AsyncData<ResT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)> = {
    data: writableComputedRef(() => nuxtApp._asyncData[key.value]?.data as Ref<ResT>),
    pending: writableComputedRef(() => nuxtApp._asyncData[key.value]?.pending as Ref<boolean>),
    status: writableComputedRef(() => nuxtApp._asyncData[key.value]?.status as Ref<AsyncDataRequestStatus>),
    error: writableComputedRef(() => nuxtApp._asyncData[key.value]?.error as Ref<NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>>),
    refresh: (...args) => nuxtApp._asyncData[key.value]!.execute(...args),
    execute: (...args) => nuxtApp._asyncData[key.value]!.execute(...args),
    clear: () => {
      options.abortController?.abort?.(new DOMException('Request aborted as the async data was cleared.', 'AbortError'))
      clearNuxtDataByKey(nuxtApp, key.value)
    },
  }

  // Allow directly awaiting on asyncData
  const asyncDataPromise = Promise.resolve(nuxtApp._asyncDataPromises[key.value]).then(() => asyncReturn) as AsyncData<ResT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
  Object.assign(asyncDataPromise, asyncReturn)

  return asyncDataPromise as AsyncData<PickFrom<DataT, PickKeys>, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError<NuxtErrorDataT>)>
}

function writableComputedRef<T> (getter: () => Ref<T>) {
  return computed({
    get () {
      return getter()?.value
    },
    set (value) {
      const ref = getter()
      if (ref) {
        ref.value = value
      }
    },
  })
}


Review comment (suggestion):
I think if an abortController is triggered, then we should also track this and avoid doing anything with the finished result of an asyncData. so it shouldn't update the data/status, just as if it were cancelled.

Code after:
        const hadData = nuxtApp._asyncData[oldKey]?.data.value !== undefined
        const wasRunning = nuxtApp._asyncDataPromises[oldKey] !== undefined

        const initialFetchOptions: AsyncDataExecuteOptions = { cause: 'initial', dedupe: options.dedupe }

        // Ensure destination container exists; read/migrate value BEFORE unregistering the old key.
        if (!nuxtApp._asyncData[newKey]?._init) {
          let initialValue: NoInfer<DataT> | undefined

          if (oldKey && hadData) {
            initialValue = nuxtApp._asyncData[oldKey]!.data.value as NoInfer<DataT>
          } else {
            initialValue = options.getCachedData!(newKey, nuxtApp, { cause: 'initial' })
            initialFetchOptions.cachedData = initialValue
          }

          nuxtApp._asyncData[newKey] = createAsyncData(nuxtApp, newKey, _handler, options, initialValue)
        }

        nuxtApp._asyncData[newKey]._deps++

        // Now it's safe to drop the old container.
        if (oldKey) {
          unregister(oldKey)
        }

        // Trigger the fetch for the new key if needed.
        if (options.immediate || hadData || wasRunning) {
          nuxtApp._asyncData[newKey].execute(initialFetchOptions)
        }

        // Release the guard after the current flush to avoid overlapping executes.
        queuePostFlushCb(() => {
          keyChanging = false
        })
      }
    }, { flush: 'sync' })

    // Params/deps watcher: keep default (pre) flush to batch multiple mutations into a single execute.
    // This preserves the "non synchronous" behavior covered by tests.
    const unsubParamsWatcher = options.watch
      ? watch(options.watch, () => {
          if (keyChanging) { return } // avoid double execute while the key switch is being processed
          asyncData._execute({ cause: 'watch', dedupe: options.dedupe })
        })
      : () => {}

    if (hasScope) {
      onScopeDispose(() => {
        unsubKeyWatcher()
        unsubParamsWatcher()