Open genericFJS opened 2 years ago
- Execute
npm run dev
and open browser: everything works fine- Install static adapter and add it in
svelte.config.js
- Execute
npm run dev
and open browser: dev server crashes with error ⚡
This series of steps seems implausible to me. It's extremely unlikely that installing any adapter would affect dev mode. Rather, I expect what's happening is that because your example depends on Math.random
it's triggered only sometimes and whether it was triggered or not was completely independent of whether the static adapter was installed.
I'm closing this since the issue reporting guidelines, which are very explicit about the requirement for a reproduction repo, were not followed
@benmccann You were right, Math.random
made the error occour only sometimes. The error does not seem to be with the static adapter.
I made a repository and narrowed the bug down: https://github.com/toolongformore/sveltekit-adapter-static-bug (sorry for forgetting to include one in the original report)
The problem arises, when a variable is initialized with an error-throwing promise; an error thrown during runtime poses no problem:
<script lang="ts">
async function getRandomNumber(throwError: boolean) {
if (throwError) {
throw new Error('error');
} else {
return 'ok';
}
}
// with this line, everything works fine
let promise = getRandomNumber(false);
// with this line, dev server crashes on start
// let promise = getRandomNumber(true);
</script>
And here's the error log:
(node:729478) UnhandledPromiseRejectionWarning: Error: error
at eval (/src/routes/index.svelte:47:11)
at Generator.next (<anonymous>)
at eval (/src/routes/index.svelte:40:67)
at new Promise (<anonymous>)
at __awaiter (/src/routes/index.svelte:17:10)
at getRandomNumber (/src/routes/index.svelte:45:10)
at eval (/src/routes/index.svelte:57:16)
at Object.$$render (/node_modules/svelte/internal/index.js:1684:22)
at Object.default (/.svelte-kit/dev/generated/root.svelte:57:127)
at eval (/.svelte-kit/dev/components/layout.svelte:8:41)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:729478) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:729478) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Like it says, you should wrap the code in the promise with a try-catch or add a {:catch error}
to your {#await}
block
Perhaps SvelteKit could use this: https://nodejs.org/api/process.html#process_event_unhandledrejection
Hmm. I'm not quite sure how to listen for unhandledRejection
in SvelteKit. We wouldn't have the request
available if we use the process.on
method, so you couldn't include the logged in user when reporting to Sentry, etc.
I tried to put a try
/catch
around the ssrLoadModule
call, but it didn't have any effect, which I think is expected, but I'm not a pro with promise error handling
Like it says, you should wrap the code in the promise with a try-catch or add a
{:catch error}
to your{#await}
block
The given example in the repository has a {:catch error}
block, this should not be the problem:
{#await promise}
<p>...waiting</p>
{:then number}
<p>The number is {number}</p>
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
Interestingly if I assign the promise in onMount
, everything is fine as well. There only seems to be a problem, when the rejecting promise is assigned at declaration:
// crashes:
let promise = getRandomNumber(true);
// works:
let promise;
onMount(()=> { promise = getRandomNumber(true); });
For a component like
{#await Promise.reject('foo')}
a
{:then}
b
{:catch}
c
{/await}
the compiler in SSR mode currently produces
/* App.svelte generated by Svelte v3.43.0 */
import { create_ssr_component, is_promise } from "svelte/internal";
const App = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `${(function (__value) {
if (is_promise(__value)) return `
a
`;
return (function () {
return `
b
`;
})(__value);
})(Promise.reject('foo'))}`;
});
export default App;
This crashes Node 16 because of the unhandled rejected promise.
If we wanted the compiled SSR code to always suppress this (by appeasing Node by making it see the promise as handled) we could make this change in the compiler:
diff --git a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
index 6a62f872b..c8b0eeb38 100644
--- a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
+++ b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
@@ -13,7 +13,10 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt
renderer.add_expression(x`
function(__value) {
- if (@is_promise(__value)) return ${pending};
+ if (@is_promise(__value)) {
+ __value.catch(() => {});
+ return ${pending};
+ }
return (function(${node.then_node ? node.then_node : ''}) { return ${then}; }(__value));
}(${node.expression.node})
`);
Or, it might be a good idea to only add this __value.catch(() => {});
when there's a catch
on the {#await}
. I'm not sure.
If we wanted to do that,
diff --git a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
index 6a62f872b..36eecf38c 100644
--- a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
+++ b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
@@ -1,6 +1,6 @@
import Renderer, { RenderOptions } from '../Renderer';
import AwaitBlock from '../../nodes/AwaitBlock';
-import { x } from 'code-red';
+import { b, x } from 'code-red';
export default function(node: AwaitBlock, renderer: Renderer, options: RenderOptions) {
renderer.push();
@@ -13,7 +13,10 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt
renderer.add_expression(x`
function(__value) {
- if (@is_promise(__value)) return ${pending};
+ if (@is_promise(__value)) {
+ ${node.catch_node != null && b`__value.catch(() => {});`}
+ return ${pending};
+ }
return (function(${node.then_node ? node.then_node : ''}) { return ${then}; }(__value));
}(${node.expression.node})
`);
would only suppress the rejected promise if there was a catch
block, and would otherwise let it crash the process if it weren't handled some other way like with process.on
.
I've created https://github.com/sveltejs/svelte/issues/6789 for the Svelte side of this. We can continue the discussion here about how to deal with other types of unhandled rejections, and whether that ought to be the framework's job or the user's job.
If the framework were to try and handle it, it would have to be in coordination with adapters, since process.on
only makes sense in Node. In order for handleError
to capture these errors we'd need to expose it as server.handleError
or something (need to think about how this would work with scoped hooks/middleware — perhaps handleError
can only be implemented in the root hook/middleware), and we wouldn't be able to associate it with an event
Do we have any sort of an idea of what other serverless hosting environments do when there's an unhandled rejection, and whether they let user code hook into that?
I have no idea, honestly
I'm facing this now, and it's supremely annoying - any update, @Rich-Harris, @Conduitry?
Describe the bug
When an error is thrown in an async function the dev server crashes with the static adapter enabled. With no adapter, the dev server works just fine. The static build seems to work just fine as well.
Reproduction
Add code similar to the solution from https://svelte.dev/tutorial/await-blocks to index.svelte:
{#await promise}
...waiting
{:then number}
The number is {number}
{:catch error}
{error.message}
{/await}
System Info
Severity
annoyance
Additional Information
No response