Repository: sveltejs/svelte (JavaScript)
Date: 2021-09-29 22:30:39+00:00
Task type: Bug Fix

Issue:
{#await} in SSR should suppress rejecting promises
### Describe the bug

(This came out of https://github.com/sveltejs/kit/issues/2520 but is a separate matter from how SvelteKit ought to deal with user code that creates unhandled rejected promises.)

Node 16 terminates the process immediately when it detects that a dangling promise has rejected and no callbacks have been attached to handle that. SSR code for an `{#await}` block ignores the promise resolution (and just renders the waiting-for-promise content), which means that Node thinks the promise has not been handled appropriately. In Node 14, this results in a noisy warning, and in Node 16 this results in the program ending with an error.

Since we specifically do not care about the promise resolution/rejection in SSR in `{#await}`s (and since there's nothing we can do about this while SSR itself is synchronous), the generated SSR code for an `{#await}` block should contain explicit (dummy) handlers for the promise, to appease Node.

### Reproduction

```svelte
{#await Promise.reject('foo')}
	bar
{/await}
```

Compiling this in SSR mode and running it in Node 16 results in the process crashing, because you are generating a promise that rejects but it is then never handled.

### Logs

```shell
[UnhandledPromiseRejection: 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(). The promise rejected with the reason "foo".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
```


### System Info

```shell
Node: 16.10.0
```


### Severity

annoyance


Issue discussion:

Solution patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ jobs:
     timeout-minutes: 15
     strategy:
       matrix:
-        node-version: [8, 10, 12, 14]
+        node-version: [8, 10, 12, 14, 16]
         os: [ubuntu-latest, windows-latest, macOS-latest]
     steps:
       - uses: actions/checkout@v1
diff --git a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts
--- 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.then(null, @noop);
+				return ${pending};
+			}
 			return (function(${node.then_node ? node.then_node : ''}) { return ${then}; }(__value));
 		}(${node.expression.node})
 	`);


Test patch:
diff --git a/test/hydration/index.ts b/test/hydration/index.ts
--- a/test/hydration/index.ts
+++ b/test/hydration/index.ts
@@ -52,6 +52,7 @@ describe('hydration', () => {
 			const cwd = path.resolve(`${__dirname}/samples/${dir}`);
 
 			compileOptions = config.compileOptions || {};
+			compileOptions.accessors = 'accessors' in config ? config.accessors : true;
 
 			const window = env();
 
diff --git a/test/hydration/samples/event-handler/main.svelte b/test/hydration/samples/event-handler/main.svelte
--- a/test/hydration/samples/event-handler/main.svelte
+++ b/test/hydration/samples/event-handler/main.svelte
@@ -1,9 +1,9 @@
 <script>
-	let clicked;
+	export let clicked;
 </script>
 
 <button on:click='{() => clicked = true}'>click me</button>
 
 {#if clicked}
 	<p>clicked!</p>
-{/if}
\ No newline at end of file
+{/if}
diff --git a/test/runtime/index.ts b/test/runtime/index.ts
--- a/test/runtime/index.ts
+++ b/test/runtime/index.ts
@@ -25,12 +25,13 @@ let compile = null;
 const sveltePath = process.cwd().split('\\').join('/');
 
 let unhandled_rejection = false;
-process.on('unhandledRejection', err => {
+function unhandledRejection_handler(err) {
 	unhandled_rejection = err;
-});
+}
 
 describe('runtime', () => {
 	before(() => {
+		process.on('unhandledRejection', unhandledRejection_handler);
 		svelte = loadSvelte(false);
 		svelte$ = loadSvelte(true);
 
@@ -46,6 +47,7 @@ describe('runtime', () => {
 
 		return setupHtmlEqual();
 	});
+	after(() => process.removeListener('unhandledRejection', unhandledRejection_handler));
 
 	const failed = new Set();