Mid run cancellation
As we hinted in the What is it? explainer one of the advantages of
@sheepdog/svelte
is that it allows you to really “cancel” a task. Let’s look at the actual problem
and how it is solves with @sheepdog/svelte
.
The problem
Promises are the de facto way to run asynchronous code in Javascript and (especially after the
introduction of the async
and await
keywords) they are quite nice to work with.
However, they have a big problem: once invoked there’s no way to stop the execution of the code. This can lead to performance problems in the simplest case or even bugs in more complex scenarios.
This is especially true if we are invoking those functions within a UI framework because we tend to assign values outside of the scope of the function to reactively show them in the ui.
The simplest way to solve this problem is to set up a variable and check it after the fetch.
This works fine but it gets tedious pretty fast, especially if you need to do it multiple times.
That’s where @sheepdog/svelte
comes into play.
The solution(s)
@sheepdog/svelte
provides you multiple tools to solve this problem (hence the parenthesized plural
in the title of this section); let’s go over them one by one
Solution 1: AbortSignal
The simplest but more verbose solution to this problem is to use AbortSignal
solution:
@sheepdog/svelte
invokes your task with a series of utils, one of which is the AbortSignal
.
Every task has its own AbortController
and you can cancel a single task instance by invoking the
cancel
method on it or cancel every instance by invoking the cancelAll
method on the task.
We’ve gained the ability to stop in-flight fetches with the AbortSignal
without having to create a
separate canceled
variable. That’s a win. But we can do better.
Solution 2: Async generators
Those who doesn’t know about generators might be a bit confused right know and those who know about them might be already running away in fear but please bear with us for a second and we will show you that generators are not really that scary.
A generator is a particular function in Javascript that is able to yield
back the execution to the
caller, the syntax to create one looks like this
I know, I told you this wouldn’t be scary and for the moment I haven’t keep my promise (pun intended). But the main takeaway from this snippet of code is to show that generator functions have a way to stop executing and return something to the caller and the caller has a way to communicate something back.
@sheepdog/svelte
has been built to be able to accept an async generator function and, most
importantly, has been built to make the generator function work basically like a normal async
function if you change await
with yield
. Let’s take a look
As you can see, the code in the two tabs the code changes very little but with generators
@sheepdog/svelte
has the ability to never call next
if the task was canceled. This means that
the if you cancel the task while fetch is still in-flight the second line of the function will
never be called!
There’s one small detail we’ve hidden from you however: yield
doesn’t work very well with
Typescript, especially if there are multiple of them. If you try to paste that code in a .ts
file
(or in a svelte component with <script lang='ts'>
) you will see all sort of errors. This is
because Typescript doesn’t know which kind of data @sheepdog/svelte
will pass back to the
generator.
To fix this problem you can use yield
as a sort of if+return
Can we do better than this? Yes we can!
Solution 3: Async Transform
@sheepdog/svelte
really cares about your DX and that’s why we have built a vite plugin that you
can use to get the best of both words: the dynamic cancellation of generators and the expressivity
and simplicity of async functions.
In short, what the vite plugin does is transform every async function inside a task
to an async
generator and it substitute every await
with a yield
. This fixes all our problems because the
Typescript language server will resolve the types based on your actual code while at runtime
@sheepdog/svelte
will be able to cancel every task, even in the middle of an execution!