Vue Concurrency Manager
vue-concurrency is a JavaScript library designed for encapsulating asynchronous operations and managing concurrency within Vue.js applications, leveraging the Composition API. Inspired by `ember-concurrency`, it provides a robust abstraction layer to reduce boilerplate associated with complex async flows. The current active version is `6.0.0-0` (a pre-release) which introduces features like global configuration and pruning, while the `5.x` series provides stable support for Vue 3.3+. Earlier versions (4.x) support Vue 2.7 and 3.2. Key differentiators include built-in TypeScript support, sophisticated async cancellation mechanisms via generator functions and the CAF library, and the ability to provide `AbortSignal` for native fetch/XHR abortion. It offers a reactive derived state (e.g., `isRunning`, `isIdle`, `isFinished`) for tracking operation status and powerful concurrency management strategies such as `drop()`, `restartable()`, and `enqueue()`. The library is actively maintained with regular updates and experimental SSR support.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'setup') OR Error: [Vue warn]: Failed to resolve component: <ComponentName>
cause `vue-concurrency` is being used in a Vue 2 project without Vue 2.7, or with an incompatible version of `@vue/composition-api`, or an incorrect Vue 3 version.fixFor Vue 2.7, ensure you are using `vue-concurrency` v4.x. For Vue 3, ensure you meet the peer dependency requirement (v5.x requires Vue 3.3+). Verify your `package.json` and installed Vue version match. -
Error: [vue-concurrency] Task 'myTask' is already running. You attempted to perform 'myTask' but it is already running. If you want to run multiple tasks concurrently, use one of the concurrency strategies like 'enqueue' or 'restartable'.
cause By default, `vue-concurrency` tasks have a `drop` concurrency strategy which prevents multiple instances of the same task from running simultaneously. Attempting to `perform()` a task that is already running will trigger this error.fixSpecify a different concurrency strategy when defining your task, such as `.enqueue()`, `.restartable()`, or `.keepLatest()`, depending on your desired behavior for concurrent task executions. Example: `useTask(...).enqueue()`. -
SyntaxError: Unexpected token '*' OR ReferenceError: regeneratorRuntime is not defined
cause Your build environment (Babel, TypeScript, Webpack, Vite) is not correctly configured to transpile ES generator functions (`function*`) for your target browser or Node.js environment, or `regenerator-runtime` is missing.fixEnsure your `tsconfig.json` `target` and `lib` settings are appropriate, and your bundler's Babel configuration includes the necessary plugins (e.g., `@babel/plugin-transform-regenerator`) or polyfills (`regenerator-runtime/runtime`) if targeting older environments.
Warnings
- breaking Version 5.x of `vue-concurrency` requires Vue 3.3 or newer. Applications using older Vue 3 versions (e.g., 3.0-3.2) or Vue 2.7 must remain on `vue-concurrency` v4.x.
- breaking Version 4.x introduced support for Vue 2.7 and 3.2, which required changes that deprecated earlier Vue 2 versions used with `@vue/composition-api`. For Vue 2 projects, only Vue 2.7 is officially supported by 4.x.
- gotcha The currently provided version `6.0.0-0` is a pre-release (`-0` suffix). While it introduces new features like global configuration and pruning, its API might not be stable, and it may contain bugs or breaking changes in subsequent `6.x` pre-releases or the final `6.0.0` release. Using pre-releases in production is not recommended for stability.
- gotcha `vue-concurrency` leverages ES generator functions (`function*`) for automatic async cancellation. Defining tasks with plain `async`/`await` functions will prevent cancellation from working correctly, leading to potential resource leaks or unexpected behavior when concurrency strategies like `drop()` or `restartable()` are applied.
- gotcha When importing TypeScript types from `vue-concurrency` (e.g., `Task`, `TaskInstance`), it's best practice to use `import type { ... } from 'vue-concurrency'`. While `import { ... } from 'vue-concurrency'` might work in some build environments, `import type` explicitly signals a type-only import, preventing potential runtime errors or unnecessary bundle size increases.
Install
-
npm install vue-concurrency -
yarn add vue-concurrency -
pnpm add vue-concurrency
Imports
- useTask
const useTask = require('vue-concurrency')import { useTask } from 'vue-concurrency' - TaskInstance
import { TaskInstance } from 'vue-concurrency'import type { TaskInstance } from 'vue-concurrency' - Task
import Task from 'vue-concurrency'
import type { Task } from 'vue-concurrency'
Quickstart
import { defineComponent, ref } from 'vue';
import { useTask } from 'vue-concurrency';
export default defineComponent({
setup() {
const searchTerm = ref('');
const searchResults = ref<string[]>([]);
const error = ref<string | null>(null);
// Define an asynchronous task for searching
const searchTask = useTask(function* (term: string) {
error.value = null;
searchResults.value = [];
if (!term) {
return;
}
try {
// Simulate an API call with a delay. `yield` makes it cancellable.
yield new Promise(resolve => setTimeout(resolve, 500));
// Simulate a potential network error
if (Math.random() < 0.2) {
throw new Error('Simulated network error!');
}
const results = Array.from({ length: 3 }, (_, i) => `${term} result ${i + 1}`);
searchResults.value = results;
} catch (e: any) {
error.value = e.message;
}
}).drop(); // Concurrency strategy: if a new search starts, previous one is cancelled/dropped
const onSearchInput = (event: Event) => {
const input = event.target as HTMLInputElement;
searchTerm.value = input.value;
// Perform the task whenever the input changes
searchTask.perform(input.value);
};
return {
searchTerm,
searchResults,
error,
searchTask,
onSearchInput
};
},
template: `
<div style="padding: 20px; font-family: sans-serif;">
<h1>Autocomplete Search</h1>
<input
type="text"
v-model="searchTerm"
@input="onSearchInput"
placeholder="Type to search..."
style="padding: 8px; font-size: 16px; width: 300px;"
/>
<div v-if="searchTask.isRunning" style="margin-top: 10px; color: gray;">Searching...</div>
<div v-if="error" style="margin-top: 10px; color: red; font-weight: bold;">Error: {{ error }}</div>
<ul v-if="searchResults.length && !searchTask.isRunning" style="list-style: none; padding: 0; margin-top: 10px;">
<li v-for="result in searchResults" :key="result" style="padding: 5px 0; border-bottom: 1px solid #eee;">{{ result }}</li>
</ul>
<div v-if="!searchTerm && !searchTask.isRunning && !error" style="margin-top: 10px; color: #aaa;">Start typing to see results</div>
</div>
`
});