summaryrefslogtreecommitdiff
path: root/shared/components/src/utils/makeSafeTick.ts
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /shared/components/src/utils/makeSafeTick.ts
init commit
Diffstat (limited to 'shared/components/src/utils/makeSafeTick.ts')
-rw-r--r--shared/components/src/utils/makeSafeTick.ts64
1 files changed, 64 insertions, 0 deletions
diff --git a/shared/components/src/utils/makeSafeTick.ts b/shared/components/src/utils/makeSafeTick.ts
new file mode 100644
index 0000000..f9ea8c2
--- /dev/null
+++ b/shared/components/src/utils/makeSafeTick.ts
@@ -0,0 +1,64 @@
+/* eslint-disable import/prefer-default-export */
+// eslint-disable-next-line no-restricted-imports
+import { tick as svelteTick, onDestroy } from 'svelte';
+
+// Unfortantely for TS to recognize that this can be awaited
+// we need to leave `Promise<void | never>` otherwise TS hints
+// will suggest removing the await.
+// See @remarks for reason to disable `then`
+type TickType = () => Omit<Promise<string>, 'then'> | Promise<void | never>;
+
+type SafeTickCallback = (tick: TickType) => Promise<void | never>;
+
+class DestroyedError extends Error {
+ constructor() {
+ super('component was destroyed before tick resolved.');
+ this.name = 'DestroyedError';
+ }
+}
+
+/**
+ * Provides a safer way to use svelte's tick helper.
+ *
+ * This prevents code that relies on tick() from running
+ * if the component is destroyed while the tick resolution
+ * is inflight.
+ *
+ * @remarks
+ * To avoid floating promises (promises with no return statements)
+ * it is safer to use the `async/await` syntax.
+ *
+ * If this is used with the `.then()` syntax without the promise
+ * being returned the DestroyedError will bubble up to sentry.
+ *
+ * @example
+ * ```ts
+ * const safeTick = makeSafeTick();
+ * onMount(async() => {
+ * await safeTick(async (tick) => {
+ * // Use tick normally
+ * await tick();
+ * // ...
+ * });
+ * });
+ * ```
+ */
+export const makeSafeTick = (): ((
+ callback: SafeTickCallback,
+) => Promise<void | never>) => {
+ let destroyed = false;
+ onDestroy(() => {
+ destroyed = true;
+ });
+
+ return async (callback) => {
+ try {
+ await callback(async () => {
+ await svelteTick();
+ if (destroyed) throw new DestroyedError();
+ });
+ } catch (e) {
+ if (!(e instanceof DestroyedError)) throw e;
+ }
+ };
+};