-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: update onMount type definition to prevent async function return #8136
fix: update onMount type definition to prevent async function return #8136
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense!
src/runtime/internal/lifecycle.ts
Outdated
@@ -31,7 +31,7 @@ export function beforeUpdate(fn: () => any) { | |||
* | |||
* https://svelte.dev/docs#run-time-svelte-onmount | |||
*/ | |||
export function onMount(fn: () => any) { | |||
export function onMount(fn: (() => () => void) | (() => void | Promise<void>)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function onMount(fn: (() => () => void) | (() => void | Promise<void>)) { | |
export function onMount(fn: (() => () => any) | (() => any | Promise<any>)) { |
In this case, this will be a type error, right?
But fundamentally, there is no problem.
// api.js
export const fetch = () => Promise.resolve({foo: 'bar'});
// Component.svelte
<script>
import { onMount } from 'svelte';
import { fetch } from './api';
onMount(() => fetch())
</script>
So IMO, it's better to use any
type instead of void
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion but I think that this won't work to prevent a function return from an async onMount.
I am attempting to cause a typescript failure on arguments of type () => Promise<() => any>
, but the introduction of () => any
means that this remains a valid argument.
Given I only actually want to disallow this single type, perhaps the following is more appropriate:
type AnyExcept<Type, Disallowed> = Type & (Type extends Disallowed ? never : Type);
export declare function onMount<T>(fn: () => AnyExcept<T, Promise<() => any>>) {
Testing locally, this allows your example above and any other return from onMount except for the following:
onMount(async() => { // fails
return () => {
/// anything
}
})
…ent-async-function-return
@baseballyama is attempting to deploy a commit to the Svelte Team on Vercel. A member of the Team first needs to authorize it. |
I updated the type. <script lang="ts">
import {onMount} from 'svelte';
// sync and no return (NO TYPE ERROR)
onMount(() => {
console.log("mounted");
});
// sync and return value (NO TYPE ERROR)
onMount(() => {
return 'done';
});
// sync and return sync (NO TYPE ERROR)
onMount(() => {
return () => {
return "done";
};
});
// sync and return async (NO TYPE ERROR)
onMount(() => {
return async () => {
const res = await fetch();
return res;
};
});
// async and no return (NO TYPE ERROR)
onMount(async () => {
await fetch();
});
// async and return value (NO TYPE ERROR)
onMount(async () => {
const res = await fetch();
return res;
});
// async and return sync (**TYPE ERROR**)
onMount(async () => {
return () => {
return "done";
};
});
// async and return async (**TYPE ERROR**)
onMount(async () => {
return async () => {
const res = await fetch();
return res;
};
});
</script> |
I added tests so we can check against regressions later. During that I noticed one case where there's now an error, which I'd like to discuss if this is a blocker from merging this, or if it's ok: If you return onMount(async () => {
const a: any = null as any;
return a;
}); How likely is it that this happens? This probably only happens in the wild by accident through something like I'd be ok with this. Please vote thumbs up/down if you think this is ok/not ok. |
If this is just types and is not a runtime check for something that looks like a promise, I think this is fine. If someone really has a good/weird reason for going that, they can add a ts-ignore. |
…turn (#8136) --------- Co-authored-by: Yuichiro Yamashita <xydybaseball@gmail.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Breaking change / how to migrate
The
onMount
type definition was updated to make returning a function (orany
, which could also be a function) asynchronously is a type error because in 90% of cases it hints at a mistake: You likely want the returned function to be called on destroy, but that does not happen for asynchronously returned functions.To migrate, adjust the code so you don't return a function asynchronously. Note that you also get the error if you return
any
becauseany
includes function types.If this change revealed an actual bug in your code:
If this change results in a false positive, just make it not return a function:
PR description
From the docs for onMount (https://svelte.dev/docs#run-time-svelte-onmount) a cleanup function can be returned from onMount which will then be used on dismount for cleanup. These only work for synchronous functions, not async.
We just discovered a few mistakes in our codebase in which the following pattern was used:
I am proposing a types change to the onMount function to only accept the following function signatures:
() => void
- async no cleanup() => Promise<void>
- async no cleanup() => (() => void)
- synchronous cleanupThis will catch mistakes where the cleanup would not be run.
Before submitting the PR, please make sure you do the following
[feat]
,[fix]
,[chore]
, or[docs]
.Tests
npm test
and lint the project withnpm run lint