@favorodera/use-promise
v1.0.3
Published
A Vue composable for managing async operations with reactive state, cancellation, and race-condition safety.
Maintainers
Readme
npm install @favorodera/use-promiseNuxt Auto-Import
// nuxt.config.ts
export default defineNuxtConfig({
imports: {
presets: [
{
from: '@favorodera/use-promise',
imports: ['usePromise'],
},
],
},
})usePromise will be available globally — no import needed in composables or components.
Usage
import { usePromise } from '@favorodera/use-promise'
const { abort, execute, reset, state } = usePromise(async (signal, id: string) => {
const response = await fetch(`/api/users/${id}`, { signal })
return response.json()
})
execute('123')API
usePromise(callback)
| Parameter | Type | Description |
| ---------- | ------------------------------------------------- | ---------------------------------------- |
| callback | (signal: AbortSignal, ...args) => Promise<TData> | Async function to manage. Receives an AbortSignal as its first argument. |
Returns
| Name | Type | Description |
| --------- | ----------------------------- | -------------------------------------------------------- |
| state | Readonly<Ref<PromiseState>> | Reactive state object |
| execute | (...args) => Promise<TData \| undefined> | Runs the callback. Cancels any in-flight request first. Returns the resolved value, or undefined on failure. |
| abort | () => void | Cancels the in-flight request silently. Does not set error state. |
| reset | () => void | Aborts and returns state to idle. |
State Shape
export type PromiseState<TData, TError extends Error = Error>
= |
{
data: null | TData | undefined
error: null | undefined
status: 'pending'
}
| {
data: null | TData | undefined
error: TError
status: 'error'
}
| {
data: null | undefined
error: null | undefined
status: 'idle'
}
| {
data: TData
error: null | undefined
status: 'success'
}data is preserved across pending and error states — no UI flicker on reload or failure.
Behaviour
Auto-cancellation — execute() aborts the previous request before starting a new one.
Race-condition safe — only the latest execution updates state, even if an earlier one resolves last.
Signal-agnostic — callbacks that don't honour AbortSignal are still protected via an internal execution ID.
Silent abort — abort() and reset() never write to state.error.
Examples
1. Fetching Data (with cancellation)
<script setup lang="ts">
import { usePromise } from '@favorodera/use-promise'
const { execute, state } = usePromise(async (signal, id: string) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`,
{ signal }
)
return response.json()
})
execute('1')
</script>
<template>
<div v-if="state.status === 'pending'">
Loading...
</div>
<div v-else-if="state.status === 'error'">
{{ state.error?.message }}
</div>
<pre v-else>{{ state.data }}</pre>
</template>2. Simple Async Task (no cancellation needed)
<script setup lang="ts">
import { usePromise } from '@favorodera/use-promise'
const { execute, state } = usePromise(async (_signal, name: string) => {
await new Promise((resolve) => {
setTimeout(resolve, 1000)
})
return `Hello, ${name}!`
})
</script>
<template>
<button @click="execute('Alice')">
Greet
</button>
<div v-if="state.status === 'pending'">
Waiting...
</div>
<div v-else-if="state.status === 'success'">
{{ state.data }}
</div>
</template>3. Search with Race Protection
<script setup lang="ts">
import { usePromise } from '@favorodera/use-promise'
import { ref, watch } from 'vue'
const idToSearch = ref('')
const { execute, state } = usePromise(async (signal, id: string) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`,
{ signal }
)
return response.json()
})
watch(idToSearch, (id) => {
if (id) execute(id)
})
</script>
<template>
<input
v-model="idToSearch"
placeholder="Enter user id to fetch"
>
<div v-if="state.status === 'pending'">
Searching...
</div>
<div v-else-if="state.status === 'error'">
{{ state.error }}
</div>
<pre v-else>{{ state.data }}</pre>
</template>5. Parallel-like Trigger (latest wins)
<script setup lang="ts">
import { usePromise } from '@favorodera/use-promise'
const { execute, state } = usePromise(async (_signal, label: string) => {
await new Promise((resolve) => {
setTimeout(resolve, Math.random() * 2000)
})
return `Finished: ${label}`
})
</script>
<template>
<button @click="execute('A')">
Run A
</button>
<button @click="execute('B')">
Run B
</button>
<div v-if="state.status === 'pending'">
Running...
</div>
<div v-else-if="state.status === 'error'">
{{ state.error }}
</div>
<pre v-else>{{ state.data }}</pre>
</template>