@favorodera/use-promise

A Vue composable for managing async operations with reactive state, cancellation, and race-condition safety.
1,225 1
Install
npm install @favorodera/use-promise
Latest Version:1.0.3
Vue:3
License:MIT
Last Updated:Oct 26, 2026
Links: GitHub  ·  npm
Maintainer: favorodera

usePromise

A Vue composable for async operations with reactive state, cancellation, and race-condition safety.

NPM Version NPM Downloads NPM Unpacked Size

npm install @favorodera/use-promise

Nuxt 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>

Related Packages

@directive-run/vue

Vue composition API adapter for Directive.

13,087 5
@vue/composition-api

Provide logic composition capabilities for Vue.

6,521,705 4,179
vue-composition-global-subscription

A Component to handle global events and data

345
@prefecthq/vue-compositions

A collection of reusable vue compositions.

86,897 7
@lehoczky/vue-keycloak

Keycloak plugin for Vue3 and Composition API

4,884

Version History

Version Released Vue License
1.0.3 ^3.0.0 MIT
1.0.2 ^3.0.0 MIT
1.0.1 ^3.0.0 MIT
1.0.0 ^3.0.0 MIT
0.0.0 ^3.0.0 MIT
Pre-releases (2)
Version Released Vue License
1.0.0-alpha.1 ^3.0.0 MIT
1.0.0-alpha.0 ^3.0.0 MIT