Repository: sveltejs/svelte (JavaScript)
Date: 2021-07-24 03:17:12+00:00
Task type: Feature

Issue:
feature request: `{#await promise then}`
<!--
If you'd like to propose an implementation for a large new feature or change then please create an RFC:
https://github.com/sveltejs/rfcs
-->

**Is your feature request related to a problem? Please describe.**
If you have a promise whose resolve value you don't care about, you can currently do:

```
{#await promise}
  loading...
{:then}
  resolved
{/await}
```

but with the shorthand you can't do:

```
{#await promise then}
  resolved
{/await}
```

When you don't need to show a loading state, there is no way to omit the variable for the promise's resolve value. This is a fairly common situation, and it would be good to handle it.

**Describe the solution you'd like**
Support the above syntax where the variable is omitted.

**Describe alternatives you've considered**
You can work around this by including a variable that you don't use:

```
{#await promise then done}
  resolved
{/await}
```

But this is awkward style and may also cause issues with linters.

**How important is this feature to you?**
Moderate. It's not a show-stopper because there's a workaround, but it also seems like something Svelte should support, and it shouldn't be very difficult.


Issue discussion:
A thought on the semantics of this, I think `{#await promise}` would make more sense than `{#await promise then}`. The `then` here doesn't have any significance in this case and could be omitted.
I'm pretty sure we need the `then`, because without it Svelte would interpret it as when the promise is pending.
I don't think that's the case, the compiler could check for the absence of `{:then name}` to determine if it's the super-shorthand or not.
I think this covers all the cases? https://svelte.dev/docs#await
> I don't think that's the case, the compiler could check for the absence of `{:then name}` to determine if it's the super-shorthand or not.

Aside from being overly magical (in my opinion) that isn't sufficient, because then you couldn't show something during loading unless you also wanted to show something after the promise was resolved. Svelte shouldn't impose peculiar restrictions like that.
> I think this covers all the cases? https://svelte.dev/docs#await

Yes, this is not handling any new cases per se. It's just allowing a simpler syntax for an existing case.
> Aside from being overly magical (in my opinion) that isn't sufficient, because then you couldn't show something during loading unless you also wanted to show something after the promise was resolved. Svelte shouldn't impose peculiar restrictions like that.

I'm not sure what you mean. Maybe we are misunderstanding each other? I only mean that we can omit the `then` in this proposed syntax because it doesn't really do anything here. It served as syntax to show which is the output in `{#await promise then value}`. Here it's just extra. Is that too magical?

> Yes, this is not handling any new cases per se. It's just allowing a simpler syntax for an existing case.

That's not what I was referring to, I just mean from a compiler POV it isn't hard to figure out which is which either way.


> I'm not sure what you mean. Maybe we are misunderstanding each other? I only mean that we can omit the `then` in this proposed syntax because it doesn't really do anything here. It served as syntax to show which is the output in `{#await promise then value}`. Here it's just extra. Is that too magical?

There are three promise states: pending, resolved, and rejected. `{#await}` can match all three. In the current syntax, this:

```
{#await promise}
  loading
{/await}
```

will match the pending state. With your proposal, there is no clean way to have an `{#await}` block that only matches a pending state. You'd have to do something like:

```
{#await promise}
  pending
{:then}
{/await}
```

So, while your idea does away with the superfluous syntax I'm talking about (`{#await promise then done}`), it introduces a new one. It also makes the syntax more confusing, because now `{#await promise}` would sometimes match the pending case and sometimes match the resolved case. My proposal doesn't contain superfluous syntax for any of the cases and you don't have to look beyond the immediate context to interpret what was intended. It also maintains backwards compatibility.
Gotcha. I didn't realize that `{#await promise} {/await}` was valid syntax in Svelte today. Given that, I think there would have to be a keyword to differentiate it like you describe to not introduce a breaking change.
+1 The syntax is straightforward and not problematic, and there would be no breaking change or anything either. So, I'm for this.

Solution patch:
diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts
--- a/src/compiler/parse/state/mustache.ts
+++ b/src/compiler/parse/state/mustache.ts
@@ -290,16 +290,24 @@ export default function mustache(parser: Parser) {
 
 		const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
 		if (await_block_shorthand) {
-			parser.require_whitespace();
-			block.value = read_context(parser);
-			parser.allow_whitespace();
+			if (parser.match_regex(/\s*}/)) {
+				parser.allow_whitespace();
+			} else {
+				parser.require_whitespace();
+				block.value = read_context(parser);
+				parser.allow_whitespace();
+			}
 		}
 
 		const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
 		if (await_block_catch_shorthand) {
-			parser.require_whitespace();
-			block.error = read_context(parser);
-			parser.allow_whitespace();
+			if (parser.match_regex(/\s*}/)) {
+				parser.allow_whitespace();
+			} else {
+				parser.require_whitespace();
+				block.error = read_context(parser);
+				parser.allow_whitespace();
+			}
 		}
 
 		parser.eat('}', true);


Test patch:
diff --git a/test/runtime/samples/await-catch-no-expression/_config.js b/test/runtime/samples/await-catch-no-expression/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/await-catch-no-expression/_config.js
@@ -0,0 +1,47 @@
+let fulfil;
+
+let thePromise = new Promise(f => {
+	fulfil = f;
+});
+
+export default {
+	props: {
+		thePromise
+	},
+
+	html: `
+		<br />
+		<p>the promise is pending</p>
+	`,
+
+	async test({ assert, component, target }) {
+		fulfil(42);
+
+		await thePromise;
+
+		assert.htmlEqual(target.innerHTML, '<br />');
+
+		let reject;
+
+		thePromise = new Promise((f, r) => {
+			reject = r;
+		});
+
+		component.thePromise = thePromise;
+
+		assert.htmlEqual(target.innerHTML, `
+			<br />
+			<p>the promise is pending</p>
+		`);
+
+		reject(new Error());
+
+		await thePromise.catch(() => {});
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>oh no! Something broke!</p>
+			<br />
+			<p>oh no! Something broke!</p>
+		`);
+	}
+};
diff --git a/test/runtime/samples/await-catch-no-expression/main.svelte b/test/runtime/samples/await-catch-no-expression/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/await-catch-no-expression/main.svelte
@@ -0,0 +1,15 @@
+<script>
+	export let thePromise;
+</script>
+
+{#await thePromise catch}
+	<p>oh no! Something broke!</p>
+{/await}
+
+<br />
+
+{#await thePromise}
+	<p>the promise is pending</p>
+{:catch}
+	<p>oh no! Something broke!</p>
+{/await}
diff --git a/test/runtime/samples/await-then-no-expression/_config.js b/test/runtime/samples/await-then-no-expression/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/await-then-no-expression/_config.js
@@ -0,0 +1,55 @@
+let fulfil;
+
+let thePromise = new Promise(f => {
+	fulfil = f;
+});
+
+export default {
+	props: {
+		thePromise
+	},
+
+	html: `
+		<br>
+		<br>
+		<p>the promise is pending</p>
+	`,
+
+	async test({ assert, component, target }) {
+		fulfil();
+
+		await thePromise;
+
+		assert.htmlEqual(target.innerHTML, `
+			<p>the promise is resolved</p>
+			<br>
+			<p>the promise is resolved</p>
+			<br>
+			<p>the promise is resolved</p>
+		`);
+
+		let reject;
+
+		thePromise = new Promise((f, r) => {
+			reject = r;
+		});
+
+		component.thePromise = thePromise;
+
+		assert.htmlEqual(target.innerHTML, `
+			<br>
+			<br>
+			<p>the promise is pending</p>
+		`);
+
+		reject(new Error('something broke'));
+		
+		await thePromise.catch(() => {});
+		
+		assert.htmlEqual(target.innerHTML, `
+			<p>oh no! something broke</p>
+			<br>
+			<br>
+		`);
+	}
+};
diff --git a/test/runtime/samples/await-then-no-expression/main.svelte b/test/runtime/samples/await-then-no-expression/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/await-then-no-expression/main.svelte
@@ -0,0 +1,23 @@
+<script>
+	export let thePromise;
+</script>
+
+{#await thePromise then}
+	<p>the promise is resolved</p>
+{:catch theError}
+	<p>oh no! {theError.message}</p>
+{/await}
+
+<br />
+
+{#await thePromise then}
+	<p>the promise is resolved</p>
+{/await}
+
+<br />
+
+{#await thePromise}
+	<p>the promise is pending</p>
+{:then}
+	<p>the promise is resolved</p>
+{/await}
\ No newline at end of file