Repository: sveltejs/svelte (JavaScript)
Date: 2021-01-02 05:08:11+00:00
Task type: Bug Fix

Issue:
Each data is duplicated when using transitions in await syntax
**Describe the bug**
I try to use transitions in each of my data ({#each}) that I get after my Promise resolves. But when I re-assign it to the variable that holds the Promise, the displayed data actually increases.

Discord link => https://discord.com/channels/457912077277855764/653341885674553345/791014063496101898

https://user-images.githubusercontent.com/16399020/102923996-adcee780-44cb-11eb-9c53-36f1e5fa968f.mp4


**Logs**
Nothing shows in the console

**To Reproduce**
https://svelte.dev/repl/99c98342054542ca9c9a82f97c0f200c?version=3.31.0
https://github.com/donnisnoni/svelte-127253y6123-bug/blob/bug/src/views/admin/rayon/Index.svelte#L54-L75
https://github.com/donnisnoni/svelte-127253y6123-bug/blob/bug/src/store/rayon.js#L22-L34

**Expected behavior**
The data is not duplicated/increased

**Information about my Svelte project:**
```yaml
  System:
    OS: Linux 5.10 Linux Mint 20 (Ulyana)
    CPU: (4) x64 Intel(R) Core(TM) i3-4005U CPU @ 1.70GHz
    Memory: 445.32 MB / 3.76 GB
    Container: Yes
    Shell: 3.1.2 - /usr/bin/fish
  Binaries:
    Node: 14.15.1 - ~/nvm/node/v14.15.1/bin/node
    Yarn: 1.22.10 - ~/nvm/node/v14.15.1/bin/yarn
    npm: 6.14.9 - ~/nvm/node/v14.15.1/bin/npm
  Browsers:
    Chrome: 87.0.4280.88
  npmPackages:
    svelte: ^3.31.0 => 3.31.0 
    vite: ^1.0.0-rc.13 => 1.0.0-rc.13 
```


**Additional context**
Add any other context about the problem here.



Issue discussion:
https://svelte.dev/repl/99c98342054542ca9c9a82f97c0f200c?version=3.31.0
after playing around in the repl
i've known a few things
- `#each` doesn't cause this ( no `#each` in above repl )
- `out` transition does
try changing `transition:name` to `in:name`(no issue) or `out:name`(this one does)

current behavior(to my understanding):
1. promise holding variable (`#await promise`) gets a new promise
2. outro starts and wait for the promise to be resolved
3. `then` node gets deleted after outro ends
4. promise resolves
5. a new node with changes comes in
6. intro starts and ends

why and when does duplication happen?
-> when the promise resolves before the outro ends

promise is usually fetching and doesn't resolve before outro ends
old node gets deleted meaning `currentNode = undefined`
then new node with promise result comes in, `currentNode = newNode`

but when it resolves before outro ends
new node comes in `currentNode = newNode`
and after that, old node gets deleted `currentNode = undefined`

so, when the promise holder gets a new promise
the outro never plays as `currentNode = undefined`
but after the promise resolves
new node comes in
old node doesn't get removed

i hope this makes sense to you and helps track the bug
pardon my poor writing and explaining skills

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 * Rework SSR store handling to subscribe and unsubscribe as in DOM mode ([#3375](https://github.com/sveltejs/svelte/issues/3375), [#3582](https://github.com/sveltejs/svelte/issues/3582), [#3636](https://github.com/sveltejs/svelte/issues/3636))
 * Fix error when removing elements that are already transitioning out ([#5789](https://github.com/sveltejs/svelte/issues/5789), [#5808](https://github.com/sveltejs/svelte/issues/5808))
+* Fix duplicate content race condition with `{#await}` blocks and out transitions ([#5815](https://github.com/sveltejs/svelte/issues/5815))
 
 ## 3.31.1
 
diff --git a/src/runtime/internal/await_block.ts b/src/runtime/internal/await_block.ts
--- a/src/runtime/internal/await_block.ts
+++ b/src/runtime/internal/await_block.ts
@@ -28,7 +28,9 @@ export function handle_promise(promise, info) {
 					if (i !== index && block) {
 						group_outros();
 						transition_out(block, 1, 1, () => {
-							info.blocks[i] = null;
+							if (info.blocks[i] === block) {
+								info.blocks[i] = null;
+							}
 						});
 						check_outros();
 					}


Test patch:
diff --git a/test/runtime/samples/transition-js-await-block-outros/_config.js b/test/runtime/samples/transition-js-await-block-outros/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-await-block-outros/_config.js
@@ -0,0 +1,174 @@
+let fulfil;
+
+export default {
+	props: {
+		promise: new Promise((f) => {
+			fulfil = f;
+		})
+	},
+	intro: true,
+
+	async test({ assert, target, component, raf }) {
+		assert.htmlEqual(target.innerHTML, '<p class="pending" foo="0.0">loading...</p>');
+
+		let time = 0;
+
+		raf.tick(time += 50);
+		assert.htmlEqual(target.innerHTML, '<p class="pending" foo="0.5">loading...</p>');
+
+		await fulfil(42);
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.0">42</p>
+			<p class="pending" foo="0.5">loading...</p>
+		`);
+
+		// see the transition 30% complete
+		raf.tick(time += 30);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.3">42</p>
+			<p class="pending" foo="0.2">loading...</p>
+		`);
+
+		// completely transition in the {:then} block
+		raf.tick(time += 70);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">42</p>
+		`);
+
+		// update promise #1
+		component.promise = new Promise((f) => {
+			fulfil = f;
+		});
+		await Promise.resolve();
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">42</p>
+			<p class="pending" foo="0.0">loading...</p>
+		`);
+
+		raf.tick(time += 100);
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="pending" foo="1.0">loading...</p>
+		`);
+
+		await fulfil(43);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="pending" foo="1.0">loading...</p>
+			<p class="then" foo="0.0">43</p>
+		`);
+
+		raf.tick(time += 100);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">43</p>
+		`);
+
+		// update promise #2
+		component.promise = new Promise((f) => {
+			fulfil = f;
+		});
+		await Promise.resolve();
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">43</p>
+			<p class="pending" foo="0.0">loading...</p>
+		`);
+
+		raf.tick(time += 50);
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.5">43</p>
+			<p class="pending" foo="0.5">loading...</p>
+		`);
+
+		await fulfil(44);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.5">43</p>
+			<p class="pending" foo="0.5">loading...</p>
+			<p class="then" foo="0.0">44</p>
+		`);
+
+		raf.tick(time += 100);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">44</p>
+		`);
+
+		// update promise #3 - quick succession
+		component.promise = new Promise((f) => {
+			fulfil = f;
+		});
+		await Promise.resolve();
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">44</p>
+			<p class="pending" foo="0.0">loading...</p>
+		`);
+
+		raf.tick(time += 40);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.6">44</p>
+			<p class="pending" foo="0.4">loading...</p>
+		`);
+
+		await fulfil(45);
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.6">44</p>
+			<p class="pending" foo="0.4">loading...</p>
+			<p class="then" foo="0.0">45</p>
+		`);
+
+		raf.tick(time += 20);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.4">44</p>
+			<p class="pending" foo="0.2">loading...</p>
+			<p class="then" foo="0.2">45</p>
+		`);
+
+		component.promise = new Promise((f) => {
+			fulfil = f;
+		});
+		await Promise.resolve();
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.4">44</p>
+			<p class="pending" foo="0.2">loading...</p>
+			<p class="then" foo="0.2">45</p>
+			<p class="pending" foo="0.0">loading...</p>
+		`);
+
+		raf.tick(time += 10);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.3">44</p>
+			<p class="pending" foo="0.1">loading...</p>
+			<p class="then" foo="0.1">45</p>
+			<p class="pending" foo="0.1">loading...</p>
+		`);
+
+		await fulfil(46);
+
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.3">44</p>
+			<p class="pending" foo="0.1">loading...</p>
+			<p class="then" foo="0.1">45</p>
+			<p class="pending" foo="0.1">loading...</p>
+			<p class="then" foo="0.0">46</p>
+		`);
+
+		raf.tick(time += 10);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.2">44</p>
+			<p class="then" foo="0.1">46</p>
+		`);
+
+		raf.tick(time += 20);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="0.3">46</p>
+		`);
+
+		raf.tick(time += 70);
+		assert.htmlEqual(target.innerHTML, `
+			<p class="then" foo="1.0">46</p>
+		`);
+	}
+};
diff --git a/test/runtime/samples/transition-js-await-block-outros/main.svelte b/test/runtime/samples/transition-js-await-block-outros/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/transition-js-await-block-outros/main.svelte
@@ -0,0 +1,20 @@
+<script>
+	export let promise;
+
+	function foo(node) {
+		return {
+			duration: 100,
+			tick: t => {
+				node.setAttribute('foo', t.toFixed(1));
+			}
+		};
+	}
+</script>
+
+{#await promise}
+	<p class='pending' transition:foo>loading...</p>
+{:then value}
+	<p class='then' transition:foo>{value}</p>
+{:catch error}
+	<p class='catch' transition:foo>{error.message}</p>
+{/await}
\ No newline at end of file