vue-async-computed icon indicating copy to clipboard operation
vue-async-computed copied to clipboard

Vue 3 support?

Open Tobiasartz opened this issue 4 years ago • 3 comments

I'm playing around with this library in Vue 3 but am getting new Promises back instead of the resolved state. Is there anyone else out there using this library within Vue 3 successfully?

Tobiasartz avatar Sep 24 '20 23:09 Tobiasartz

I have the same problem with migrating to vue3. Wile the concept of this library would still work there, it is much simpler to write it using the composite API. This avoids having to install it globally (=> use in libraries, tree shaking) and makes some code cleaner.

This is my current version, written in Typescript

import {reactive, ComputedRef, Ref, computed, watchEffect, WatchStopHandle} from "vue";

enum AsyncStatus {
  ERROR = 'error', SUCCESS = 'success', LOADING = 'loading'
}

interface AsyncAccess<T> extends ComputedRef<T> {
  status: Ref<AsyncStatus>,
  error: Ref<any>,
  retry: () => void
}

export function asyncComputed<T> (func: (() => Promise<T>), options: {default?: T | Ref<T>, lazy?: boolean} = {}): AsyncAccess<T | undefined> {
  const optionsWithDefaults = {
    default: undefined,
    lazy: false,
    ...options
  };
  const state = reactive({
    result: undefined as any | undefined,
    status: AsyncStatus.LOADING,
    error: null as any,
    hasEverRun: false
  });
  let hasEverRequested = false;
  const result = computed<T | undefined>(() => {
    if (!hasEverRequested) {
      retry();
    }
    if (state.hasEverRun) {
      return state.result;
    } else {
      return options.default as T | undefined;
    }
  })

  let lastRetryCalled: Symbol | null = null
  let stopLast: WatchStopHandle | null = null;

  const retry = () => {
    hasEverRequested = true;
    if (stopLast) {
      stopLast();
      stopLast = null;
    }

    stopLast = watchEffect(() => {
      let me = Symbol('retry');
      lastRetryCalled = me;
      state.status = AsyncStatus.LOADING
      state.error = null;
      func().then(value => {
        if (lastRetryCalled === me) {
          state.status = AsyncStatus.SUCCESS
          state.result = value;
          state.error = null;
          state.hasEverRun = true;
        }
      }, error => {
        if (lastRetryCalled === me) {
          state.status = AsyncStatus.ERROR;
          state.error = error;
          state.hasEverRun = true;
        }
      });
    })
  }

  if (!optionsWithDefaults.lazy) {
    retry()
  }

  Object.defineProperty(result, 'status', {
    value: computed(() => state.status)
  });
  Object.defineProperty(result, 'error', {
    value: computed(() => state.error)
  })
  Object.defineProperty(result, 'retry', {
    value: retry
  })
  return result as AsyncAccess<T>;
}

Use it like this:

  setup () {
    const test = reactive({ counter: 1 })
    const aValue = asyncComputed(async () => {
      console.log('Computing eager value')
      const myCounter = test.counter
      await new Promise(resolve => setTimeout(resolve, 2000))
      return myCounter
    }, { default: 0 })

    const lazyValue = asyncComputed(async () => {
      console.log('Computing lazy value')
      const myCounter = test.counter
      await new Promise(resolve => setTimeout(resolve, 2000))
      return myCounter
    }, { lazy: true })

    return {
      test, // < current value of test
      aValue, // < in the UI, you can write {{ aValue }} to access current value
      aStatus: aValue.status, // < allows you to use v-if="aStatus === 'loading'"
      lazyValue // use as {{ lazyValue }} in the UI. The value will only load after you attempted to access it
    }
  }

michaelzangl avatar Oct 25 '20 18:10 michaelzangl

This is my current version, written in Typescript

Thanks a lot, that works like a charm! I only had to make it export default function instead of export function to be able to import in Vue components. Wondering if it is coming out-of-the-box any time soon. It should really.

greendrake avatar Jan 07 '21 12:01 greendrake

@Tobiasartz

In vue3 use can use VueUse to deal with async computed properties

athulanilthomas avatar Feb 22 '22 05:02 athulanilthomas

They seem to have recently renamed asyncComputed to computedAsync, so the new link is VueUse.

 npm install --save @vueuse/core
import { ref } from 'vue'
import { computedAsync } from '@vueuse/core'

const name = ref('jack')

const userInfo = computedAsync(
  async () => {
    return await mockLookUp(name.value)
  },
  null, // initial state
)

luckydonald avatar Dec 03 '22 22:12 luckydonald

At the moment (in migration phase) in our project its not a simple as just using asyncComputed from vueuse, tho that will be a refactor goal after initial migration.

Is there any actual reason it wouldn't work in vue3 w/ compat mode? I forked so I could just allow the peerDependency to allow "vue": "2 - 3" still in the process of migration but so far it seems fine.

rhysburnie avatar Dec 09 '22 01:12 rhysburnie