Repository: sveltejs/svelte (JavaScript)
Date: 2019-07-01 00:52:17+00:00
Task type: Bug Fix

Issue:
running {#await} inside an {#if}
[REPL](https://svelte.technology/repl?version=2.6.5&gist=864c4b0251414988e23730b23147ce71)

If we have a `{#await}` block running inside a truthy `{#if}` and, for some reason, the `{#if}` evaluates to false while the promise is still running, we get an error:

```
Uncaught (in promise) TypeError: Cannot read property 'removeChild' of null
    at detachNode (eval at createComponent (repl.1.js:1), <anonymous>:296:19)
    at Object.destroy [as d] (eval at createComponent (repl.1.js:1), <anonymous>:116:6)
    at update (eval at createComponent (repl.1.js:1), <anonymous>:332:17)
    at promise.then.value (eval at createComponent (repl.1.js:1), <anonymous>:347:5)
```




Issue discussion:
I think it makes sense to work on this right after or alongside #1514, because that's going to touch the await block code anyway. I just have yet to find a way of dealing with that issue that I'm happy with.
Using **svelte": "2.13.2"**

I'm facing same issue:

```
{#if $someStoreData}
  <componentA />
{:else}
  <componentB />
  <componentC />
{/if}
```

Initially, `componentB` and `componentC` are rendered. On `$someStoreData` becoming true, `componentA` is getting rendered fine. But the other components are creating trouble by getting destroyed twice. So, second time when it starts to destroy, it throws error: `Cannot read property 'removeChild' of null`.

**Any idea, how to resolve?**

---


I tried the same thing with following:

```
{#await promise}
	<ComponentA />
{:then answer}
         <ComponentB />
         <Component C/>
{:catch error}
         <ComponentB />
         <Component C/>>
{/await}

<script>
	export default {
		data() {
			return {
				promise: new Promise(fulfil => {
					setTimeout(() => fulfil(42), 3000);
				})
			};
		}
	};
</script>

```

Same issue is there. It tries to remove the element twice. 

In the following code:
```
  function detachNode(node) {
    node.parentNode.removeChild(node);
  }
```

First time when it detaches Node, node is getting destroyed rightly but it automatically tries to detach second time as well. 2nd time when it does, it is Text Node:

![image](https://user-images.githubusercontent.com/6055628/45173244-39ea7180-b225-11e8-9327-31c610e58797.png)


**Issue also comes with await example in documentation.**

After few hours of trying, got a resolution.
Solution:
Wrap those elements inside div element. Therefore:
```
<div>
  {#if $someStoreData}
    <ComponentA />
  {:else}
    <ComponentB />
    <ComponentC />
  {/if}
</div>
```

Why?
I tried to go for alternate solution, which was:

```
{#if $someStoreData}
  <componentA />
{/if}
<!-- Inside corresponding files for below, as per condtion, add class to "display:none" -->
<componentB />
<componentC />
```

Results were same, but it popped up different error this time, which was of `insertBefore` of null. It essentially wanted some DOM to be present. Once, it also gave error on `removeChild` of null which was `node.parentNode(target..)`. It essentially meant that it was trying to find relative paths corresponding to node (other Component)in the same file. So, only solution was to wrap it in parent div, so **it doesn't have** to find relative path w.r.t other siblings which were getting removed, hence error.
How's the progress on this? I've just been avoiding `{#await}` in Svelte 3, cause the wrapping div fix no longer works.
You may be able to apply this quick hack to get the project running: https://github.com/sveltejs/svelte/issues/2086#issuecomment-490989491
The original gist is 404ing — does anyone know if it's still happening, and can repro?
IIRC the original repro - converted into v3 - was something like this:

```svelte
<script>
	let promise = new Promise(res => setTimeout(res, 2000));
	let flag = true;
	setTimeout(() => flag = false, 1000);
</script>

{#if flag}
	{#await promise then _}DONE{/await}
{/if}
```

which does seem to still be an issue. We're trying to do `target.insertBefore` when `target` is null.

Solution patch:
diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts
--- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts
+++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts
@@ -137,6 +137,7 @@ export default class AwaitBlockWrapper extends Wrapper {
 		const info_props = [
 			'ctx',
 			'current: null',
+			'token: null',
 			this.pending.block.name && `pending: ${this.pending.block.name}`,
 			this.then.block.name && `then: ${this.then.block.name}`,
 			this.catch.block.name && `catch: ${this.catch.block.name}`,
@@ -223,6 +224,7 @@ export default class AwaitBlockWrapper extends Wrapper {
 
 		block.builders.destroy.add_block(deindent`
 			${info}.block.d(${parent_node ? '' : 'detaching'});
+			${info}.token = null;
 			${info} = null;
 		`);
 


Test patch:
diff --git a/test/runtime/index.js b/test/runtime/index.js
--- a/test/runtime/index.js
+++ b/test/runtime/index.js
@@ -21,6 +21,11 @@ let compile = null;
 
 const sveltePath = process.cwd().split('\\').join('/');
 
+let unhandled_rejection = false;
+process.on('unhandledRejection', err => {
+	unhandled_rejection = err;
+});
+
 describe("runtime", () => {
 	before(() => {
 		svelte = loadSvelte(false);
@@ -60,6 +65,8 @@ describe("runtime", () => {
 				throw new Error('skipping test, already failed');
 			}
 
+			unhandled_rejection = null;
+
 			compile = (config.preserveIdentifiers ? svelte : svelte$).compile;
 
 			const cwd = path.resolve(`test/runtime/samples/${dir}`);
@@ -165,10 +172,18 @@ describe("runtime", () => {
 							raf
 						})).then(() => {
 							component.$destroy();
+
+							if (unhandled_rejection) {
+								throw unhandled_rejection;
+							}
 						});
 					} else {
 						component.$destroy();
 						assert.htmlEqual(target.innerHTML, "");
+
+						if (unhandled_rejection) {
+							throw unhandled_rejection;
+						}
 					}
 				})
 				.catch(err => {
diff --git a/test/runtime/samples/await-in-removed-if/_config.js b/test/runtime/samples/await-in-removed-if/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/await-in-removed-if/_config.js
@@ -0,0 +1,22 @@
+let fulfil;
+
+const promise = new Promise(f => {
+	fulfil = f;
+});
+
+export default {
+	props: {
+		promise
+	},
+
+	html: ``,
+
+	async test({ assert, component, target }) {
+		component.condition = false;
+
+		fulfil();
+		await new Promise(f => setTimeout(f, 0));
+
+		assert.htmlEqual(target.innerHTML, ``);
+	}
+};
\ No newline at end of file
diff --git a/test/runtime/samples/await-in-removed-if/main.svelte b/test/runtime/samples/await-in-removed-if/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/await-in-removed-if/main.svelte
@@ -0,0 +1,8 @@
+<script>
+	export let promise;
+	export let condition = true;
+</script>
+
+{#if condition}
+	{#await promise then _}hello{/await}
+{/if}
\ No newline at end of file