Appearance
Cancelation ​
Because tasks utilize generator functions, they are cancelable. That prevents unnecessary work, makes them safe to use and allows new (easier) ways solve problems.
Tasks are canceled automatically when the component they exist on is unmounted or with the usage of a task.restartable()
concurrency policy.
They can be also canceled explicitly via methods like taskInstance.cancel()
or task.cancelAll()
.
Example ​
Let's create a task that periodically polls the server every 5s. With the possibility of cancelation, we don't have to use recursions or defensive guards. It's possible to create a task with an infinite loop and rely on cancelation.
ts
setup() {
const latestData = ref(null);
const getLatestTask = useTask(function*() {
while (true) {
latestData.value = yield get('/api/news');
yield timeout(5000); // wait 5s
}
}).drop();
// 👆such a task is fine to do. It will get canceled when the component is unmounted.
getLatestTask.perform(); // start polling right away
// if needed, you can pass methods to pause and resume the task to the template
function pause() {
getLatestTask.cancelAll();
}
function resume() {
// a plain perform like that is safe because the task is set to `drop()`
// therefore it won't start a new instance if it's already running
getLatestTask.perform();
}
return { getLatestTask, pause, resume };
}
Aborting Network Requests ​
vue-concurrency
uses CAF under the hood for the actual cancelation. When a task is performed, an AbortSignal is passed as the first argument to the generator function. This signal object can be passed to Fetch so that when a task is canceled the associated network requests are aborted. This saves browser some work and frees memory.
To make aborting work, we need to pass the signal to the right place:
ts
const getUserTask = useTask(function*(signal, id) {
const response = yield fetch(`/api/users/${id}`, { signal });
return response.json();
});
Axios ​
Such AbortSignal
works with fetch
right out of the box. To cancel with Axios
you can use a promise that is placed on the abort signal:
ts
const getUserTask = useTask(function*(signal, id) {
yield $axios(`/api/users/${id}`, {
cancelToken: new $axios.CancelToken((cancel) => {
signal.pr.catch((reason) => {
if (reason === "cancel") {
cancel();
}
});
}),
});
});
(AbortSignal
by itself does not have this pr
promise present. It's been placed there by CAF. That's why vue-c
uses type AbortSignalWithPromise
for this object).
Because Axios
is frequently used with Vue apps, vue-concurrency
provides a helper function that does exactly what's been shown above.
ts
import { getCancelToken } from 'vue-concurrency';
setup() {
const getUserTask = useTask(function*(signal, id) {
yield $axios(`/api/users/${id}`, {
cancelToken: getCancelToken($axios, signal);
});
});
}
Cancelation Cascade ​
If a task (main task), performs different tasks (child tasks) and gets canceled, even the child tasks should get canceled.
This cascade of cancelation is possible via taskInstance.canceledOn()
.
ts
const searchTask = useTask(function*(signal, { query }) {
const events = yield searchEvents.perform(query).canceledOn(signal);
const users = yield searchUsers.perform(query).canceledOn(signal);
return { events, users };
});
Now if that specific task instance of registerTask
gets cancelled, both the particular instances validateEmailTask
and createUserTask
will get cancelled too.
TIP
cancelOn()
is generally needed only when you deal with explicit cancelation (calling cancel()
, cancelAll()
) or when the task is restartable()
. In the case of component unmounting, all the task instances are canceled on their own.
Example ​
The time is
vue
<script lang="ts">
export default defineComponent({
setup() {
const time = ref(null);
const updateTimeTask = useTask(function*() {
// wait some time to simulate a network request
while (true) {
time.value = new Date();
yield timeout(1000);
}
});
updateTimeTask.perform();
return {
time,
updateTimeTask,
};
},
});
</script>
<template>
<div>
<hr />
<div>The time is {{ time }}</div>
<hr />
<button :disabled="updateTimeTask.isIdle" @click="updateTimeTask.cancelAll">
Pause
</button>
<button
:disabled="updateTimeTask.isRunning"
@click="updateTimeTask.perform"
>
Resume
</button>
</div>
</template>