Skip to content
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

"finally" after await block #5695

Open
ItalyPaleAle opened this issue Nov 19, 2020 · 11 comments
Open

"finally" after await block #5695

ItalyPaleAle opened this issue Nov 19, 2020 · 11 comments
Labels
PRs welcome we're not going to spend any time on this, but we'll accept pull requests
Milestone

Comments

@ItalyPaleAle
Copy link
Contributor

Describe the solution you'd like
In Svelte 3, the syntax for await blocks is modeled after the one for promises: await … then … catch.

In the specification for Promise, however, there's also one more callback: finally. This is executed after the then or catch callbacks, regardless of whether the Promise was fulfilled or rejected. Note that per specs, finally callbacks do not receive any argument.

How important is this feature to you?
Not incredibly important, but it would allow reducing code duplication. For example, look at this:

{#await p}
  Waiting…
{:then _}
  Fulfilled!!
{:catch _}
  Rejected!!!
{:finally}
  Now let's show a form here
{/await}

Without {:finally}, we'd have to add the last sentence to both the {:then} and {:catch} blocks

@Conduitry
Copy link
Member

I'm -1 on this. This feels like adding a feature for the sake of parity with the Promise API, and not for the sake of what's generally useful. The situations where you'll want to have content that appears once a promise settles directly following content that appears when it resolves/rejects seem fairly specific. And you could achieve the same result with another {#await p.catch(() => {})}.

@ItalyPaleAle
Copy link
Contributor Author

Those are fair points. Thanks for considering this suggestion anyways!

@rsdavis
Copy link

rsdavis commented Nov 21, 2020

Nice idea @ItalyPaleAle. I think that parity with the Promise API is what gives #await 100% of its value. The functionality that it offers can be achieved via other methods, so its value is derived completely from the reduction in code and complexity that results from such a close to match the API. It makes sense that #await would match the execution flow of promises as closely as possible.

@zimmah
Copy link

zimmah commented Dec 8, 2020

I agree that I am missing {:finally}

it's a bit strange that you'd have to go with workarounds when I thought svelte was all about elegance and simplicity.

@Conduitry
Copy link
Member

This has gotten three thumbs down from maintainers, and I am going to close it.

I will also note that adding a feature that lets you write less code in a certain situation is not an example elegance or simplicity if it's a rather uncommon situation.

@brandonmcconnell
Copy link
Contributor

brandonmcconnell commented Mar 8, 2022

I would like to add my own +1 here and point out that in several other feature suggestions I've seen, parity with the native APIs is often used as strong grounds for/against implementing a feature. I think the same should be true here. Finally was implemented as part of the official JavaScript spec because TC39 discussed it at length and found it to be generally useful.

I could be mistaken about the spec and approval process here, but this is how I understand it.

The same concept applied to Svelte's await pattern might look something like this:

<script>
    async function getRandomNumber() {
        const res = await fetch(`https://svelte.dev/tutorial/random-number`);
        const text = await res.text();

        if (res.ok) {
            return text;
        } else {
            throw new Error(text);
        }
    }
    
    let promise = getRandomNumber();

    function handleClick() {
        promise = getRandomNumber();
    }
</script>

<button on:click={handleClick}>
    generate random number
</button>

{#await promise}
    <p>...waiting</p>
{:then number}
    <p>The number is {number}</p>
{:catch error}
    <p style="color: red">{error.message}</p>
{:finally}
    The operation has concluded.
{/await}

Here is a REPL demonstrating the same logic using the existing await pattern paired with an if block.

In this workaround, a boolean value, defaulted to false, updates to true after the promise has been fully evaluated (using a try...catch block), then resets to false if the promise is reevaluated.

I'm sure there is probably a better way to demonstrate this, but I think the REPL linked above sufficiently serves to demonstrate the desired functionality.

@brandonmcconnell
Copy link
Contributor

@Rich-Harris You asked me to raise an issue for this a while back (Twitter comment), hence my comment, but I haven't received any reply for this yet. Should I open a new/duplicate issue.

@jspirit1
Copy link

jspirit1 commented Mar 16, 2023

I think Svelte should have a {:finally} It's a pain to do it manually with a workaround, and not a big deal to add it as a feature and it seems more elegant to have it. +1 for having it

@emanuele-scarsella
Copy link

I personally +1 this since:

  1. many want it.
  2. newcomers expect it.
  3. It's completely optional, don't you like/need it? just don't use it.

In the meantime for those who came here looking for an quick workaround, here it is a simple drop-in component that achieves the same result.

Component:

Await.svelte

<!--
 @component
 Adds an additional `finally` slot to the typical `{#await expression}...{:then name}...{:catch name}...{/await}` block.
-->
<script lang="ts">
	/** The promise to be awaited. */
	export let promise: Promise<unknown>;
</script>

{#await promise}
	<slot name="await" />
{:then result}
	<slot name="then" {result} />
	<slot name="finally" {result} error={undefined} />
{:catch error}
	<slot name="catch" {error} />
	<slot name="finally" {error} result={undefined} />
{/await}

Usage example:

App.svelte

<script lang="ts">
	import Await from './Await.svelte';
	
	let myPromise: Promise<unknown>;
</script>
<Await promise={myPromise}>
	<div slot="await">
		<p>processing...</p>
	</div>
	<div slot="catch" let:error={error}>
		<p>{error.message}</p>
	</div>
	<div slot="then" let:result={result}>
		<p>{result}</p>
	</div>
	<div slot="finally" let:error={error} let:result={result}>
		<p>{error.message}</p> <!-- undefined on success -->
		<p>{result}</p> <!-- undefined on fail -->
	</div>
</Await>

Not a perfect solution, syntax is more bloated but it does the trick for me ¯\_(ツ)_/¯

@VismaTobbe
Copy link

+1 for creative workaround :)

@oscarhermoso
Copy link

Agree using all of {#await}, {:catch} and {:finally} at the same time would be kind of silly, but this would still be nice to have for scenarios with only {#await} and {#finally}.

Eg. consider this code where I swap a search icon with a loading spinner:

	<div class="input-group>
		<span class="input-group-text">
			{#await searching}
				<div class="spinner-border spinner-border-sm" role="status">
					<span class="visually-hidden">Loading...</span>
				</div>
			{:then}
				<i class="bi bi-search"></i>
			{:catch}
				<i class="bi bi-search"></i>
			{/await}
		</span>
		<input
			type="text"
			class="form-control"
			class:disabled
			on:input={handleInput}
			bind:value={query}
			placeholder={'Search by name or email address'}
			{disabled}
		/>
	</div>

<!-- error handled elsewhere -->
{#if errorMessage}
	<div class="invalid-feedback d-block">{errorMessage}</div>
{/if}

Would be nicer as

{#await loading}
  <!-- spinner -->
{:finally}
  <!-- search icon -->
{/if}

@Rich-Harris Rich-Harris reopened this Apr 30, 2024
@Rich-Harris Rich-Harris added the PRs welcome we're not going to spend any time on this, but we'll accept pull requests label Apr 30, 2024
@Rich-Harris Rich-Harris added this to the 5.x milestone Apr 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PRs welcome we're not going to spend any time on this, but we'll accept pull requests
Projects
None yet
Development

No branches or pull requests

10 participants