vue-async-computed
vue-async-computed copied to clipboard
Vue 3 support?
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?
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
}
}
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.
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
)
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.