From 6aa254ab0b075d0227c77ff9ea2793c029145ace Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 5 May 2018 11:13:06 -0400 Subject: [PATCH 1/6] add missing Block property --- src/compile/dom/Block.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index 5c48d8ba00..1725e598c7 100644 --- a/src/compile/dom/Block.ts +++ b/src/compile/dom/Block.ts @@ -39,6 +39,7 @@ export default class Block { destroy: CodeBuilder; }; + maintainContext: boolean; hasIntroMethod: boolean; hasOutroMethod: boolean; outros: number; From 46c6b4bfe364c1841d659dc8c403fc575038bb08 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 5 May 2018 13:17:43 -0400 Subject: [PATCH 2/6] handle await block promises in a shared helper --- src/compile/nodes/AwaitBlock.ts | 93 ++++++++++++--------------------- src/shared/await-block.js | 55 +++++++++++++++++++ src/shared/index.js | 5 +- src/shared/utils.js | 4 ++ 4 files changed, 93 insertions(+), 64 deletions(-) create mode 100644 src/shared/await-block.js diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts index 60ce1ad23d..42c6ccd394 100644 --- a/src/compile/nodes/AwaitBlock.ts +++ b/src/compile/nodes/AwaitBlock.ts @@ -77,6 +77,7 @@ export default class AwaitBlock extends Node { const { snippet } = this.expression; + const info = block.getUniqueName(`info`); const promise = block.getUniqueName(`promise`); const resolved = block.getUniqueName(`resolved`); const await_block = block.getUniqueName(`await_block`); @@ -100,71 +101,38 @@ export default class AwaitBlock extends Node { block.maintainContext = true; + const infoProps = [ + block.alias('component') === 'component' ? 'component' : `component: #component`, + 'ctx', + 'current: null', + create_pending_block && `pending: ${create_pending_block}`, + create_then_block && `then: ${create_then_block}`, + create_catch_block && `catch: ${create_catch_block}`, + create_then_block && `value: '${this.value}'`, + create_catch_block && `error: '${this.error}'` + ].filter(Boolean); + + block.builders.init.addBlock(deindent` + let ${info} = { + ${infoProps.join(',\n')} + }; + `); + // the `#component.root.set({})` below is just a cheap way to flush // any oncreate handlers. We could have a dedicated `flush()` method // but it's probably not worth it block.builders.init.addBlock(deindent` - function ${replace_await_block}(${token}, type, ctx) { - if (${token} !== ${await_token}) return; - - var ${old_block} = ${await_block}; - ${await_block} = type && (${await_block_type} = type)(#component, ctx); - - if (${old_block}) { - ${old_block}.u(); - ${old_block}.d(); - ${await_block}.c(); - ${await_block}.m(${updateMountNode}, ${anchor}); - - #component.root.set({}); - } - } - - function ${handle_promise}(${promise}) { - var ${token} = ${await_token} = {}; - - if (@isPromise(${promise})) { - ${promise}.then(function(${value}) { - ${this.value ? deindent` - ${resolved} = { ${this.value}: ${value} }; - ${replace_await_block}(${token}, ${create_then_block}, @assign(@assign({}, ctx), ${resolved})); - ` : deindent` - ${replace_await_block}(${token}, null, null); - `} - }, function (${error}) { - ${this.error ? deindent` - ${resolved} = { ${this.error}: ${error} }; - ${replace_await_block}(${token}, ${create_catch_block}, @assign(@assign({}, ctx), ${resolved})); - ` : deindent` - ${replace_await_block}(${token}, null, null); - `} - }); - - // if we previously had a then/catch block, destroy it - if (${await_block_type} !== ${create_pending_block}) { - ${replace_await_block}(${token}, ${create_pending_block}, ctx); - return true; - } - } else { - ${resolved} = { ${this.value}: ${promise} }; - if (${await_block_type} !== ${create_then_block}) { - ${replace_await_block}(${token}, ${create_then_block}, @assign(@assign({}, ctx), ${resolved})); - return true; - } - } - } - - ${handle_promise}(${promise} = ${snippet}); + @handlePromise(${promise} = ${snippet}, ${info}); `); block.builders.create.addBlock(deindent` - ${await_block}.c(); + ${info}.block.c(); `); if (parentNodes) { block.builders.claim.addBlock(deindent` - ${await_block}.l(${parentNodes}); + ${info}.block.l(${parentNodes}); `); } @@ -172,7 +140,8 @@ export default class AwaitBlock extends Node { const anchorNode = parentNode ? 'null' : 'anchor'; block.builders.mount.addBlock(deindent` - ${await_block}.m(${initialMountNode}, ${anchorNode}); + ${info}.block.m(${initialMountNode}, ${info}.anchor = ${anchorNode}); + ${info}.mount = () => ${updateMountNode}; `); const conditions = []; @@ -184,7 +153,11 @@ export default class AwaitBlock extends Node { conditions.push( `${promise} !== (${promise} = ${snippet})`, - `${handle_promise}(${promise}, ctx)` + `@handlePromise(${promise}, ${info})` + ); + + block.builders.update.addLine( + `${info}.ctx = ctx` ); if (this.pending.block.hasUpdateMethod) { @@ -192,25 +165,25 @@ export default class AwaitBlock extends Node { if (${conditions.join(' && ')}) { // nothing } else { - ${await_block}.p(changed, @assign(@assign({}, ctx), ${resolved})); + ${info}.block.p(changed, @assign(@assign({}, ctx), ${info}.resolved)); } `); } else { block.builders.update.addBlock(deindent` if (${conditions.join(' && ')}) { - ${await_block}.c(); - ${await_block}.m(${anchor}.parentNode, ${anchor}); + ${info}.block.c(); + ${info}.block.m(${anchor}.parentNode, ${anchor}); } `); } block.builders.unmount.addBlock(deindent` - ${await_block}.u(); + ${info}.block.u(); `); block.builders.destroy.addBlock(deindent` ${await_token} = null; - ${await_block}.d(); + ${info}.block.d(); `); [this.pending, this.then, this.catch].forEach(status => { diff --git a/src/shared/await-block.js b/src/shared/await-block.js new file mode 100644 index 0000000000..aa1bd8ef9c --- /dev/null +++ b/src/shared/await-block.js @@ -0,0 +1,55 @@ +import { assign, isPromise } from './utils.js'; + +export function handlePromise(promise, info, mount, anchor) { + var token = info.token = {}; + + function update(type) { + if (info.token !== token) return; + + const child_ctx = assign(assign({}, info.ctx), info.resolved); + const block = type && (info.current = type)(info.component, child_ctx); + + if (info.block) { + info.block.u(); + info.block.d(); + block.c(); + block.m(info.mount(), info.anchor); + + info.component.root.set({}); + } + + info.block = block; + } + + if (isPromise(promise)) { + promise.then(value => { + if (info.value) { + info.resolved = { [info.value]: value }; + update(info.then); + } else { + info.resolved = null; + update(null); + } + }, error => { + if (info.error) { + info.resolved = { [info.error]: error }; + update(info.catch); + } else { + info.resolved = null; + update(null); + } + }); + + // if we previously had a then/catch block, destroy it + if (info.current !== info.pending) { + update(info.pending); + return true; + } + } else { + info.resolved = { [info.value]: promise }; + if (info.current !== info.then) { + update(info.then); + return true; + } + } +} \ No newline at end of file diff --git a/src/shared/index.js b/src/shared/index.js index 774849f027..a2b1ba3576 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -1,5 +1,6 @@ import { assign } from './utils.js'; import { noop } from './utils.js'; +export * from './await-block.js'; export * from './dom.js'; export * from './keyed-each.js'; export * from './spread.js'; @@ -136,10 +137,6 @@ export function _unmount() { if (this._fragment) this._fragment.u(); } -export function isPromise(value) { - return value && typeof value.then === 'function'; -} - export var PENDING = {}; export var SUCCESS = {}; export var FAILURE = {}; diff --git a/src/shared/utils.js b/src/shared/utils.js index 45260d49bc..f9fbf80e66 100644 --- a/src/shared/utils.js +++ b/src/shared/utils.js @@ -8,4 +8,8 @@ export function assign(tar, src) { export function assignTrue(tar, src) { for (var k in src) tar[k] = 1; return tar; +} + +export function isPromise(value) { + return value && typeof value.then === 'function'; } \ No newline at end of file From f6a100863bf4cfd5b024c1a4c60c7d7331b55fff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 5 May 2018 13:21:24 -0400 Subject: [PATCH 3/6] tidy up --- src/compile/nodes/AwaitBlock.ts | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts index 42c6ccd394..499e848436 100644 --- a/src/compile/nodes/AwaitBlock.ts +++ b/src/compile/nodes/AwaitBlock.ts @@ -79,25 +79,8 @@ export default class AwaitBlock extends Node { const info = block.getUniqueName(`info`); const promise = block.getUniqueName(`promise`); - const resolved = block.getUniqueName(`resolved`); - const await_block = block.getUniqueName(`await_block`); - const await_block_type = block.getUniqueName(`await_block_type`); - const token = block.getUniqueName(`token`); - const await_token = block.getUniqueName(`await_token`); - const handle_promise = block.getUniqueName(`handle_promise`); - const replace_await_block = block.getUniqueName(`replace_await_block`); - const old_block = block.getUniqueName(`old_block`); - const value = block.getUniqueName(`value`); - const error = block.getUniqueName(`error`); - const create_pending_block = this.pending.block.name; - const create_then_block = this.then.block.name; - const create_catch_block = this.catch.block.name; - - block.addVariable(await_block); - block.addVariable(await_block_type); - block.addVariable(await_token); + block.addVariable(promise); - block.addVariable(resolved); block.maintainContext = true; @@ -105,11 +88,11 @@ export default class AwaitBlock extends Node { block.alias('component') === 'component' ? 'component' : `component: #component`, 'ctx', 'current: null', - create_pending_block && `pending: ${create_pending_block}`, - create_then_block && `then: ${create_then_block}`, - create_catch_block && `catch: ${create_catch_block}`, - create_then_block && `value: '${this.value}'`, - create_catch_block && `error: '${this.error}'` + 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}`, + this.then.block.name && `value: '${this.value}'`, + this.catch.block.name && `error: '${this.error}'` ].filter(Boolean); block.builders.init.addBlock(deindent` @@ -182,8 +165,8 @@ export default class AwaitBlock extends Node { `); block.builders.destroy.addBlock(deindent` - ${await_token} = null; ${info}.block.d(); + ${info} = null; `); [this.pending, this.then, this.catch].forEach(status => { From 813862713ccf134451a8527844835f80369fab11 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 5 May 2018 13:32:12 -0400 Subject: [PATCH 4/6] simplify --- src/shared/await-block.js | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/shared/await-block.js b/src/shared/await-block.js index aa1bd8ef9c..c216f1e92a 100644 --- a/src/shared/await-block.js +++ b/src/shared/await-block.js @@ -1,11 +1,13 @@ import { assign, isPromise } from './utils.js'; -export function handlePromise(promise, info, mount, anchor) { +export function handlePromise(promise, info) { var token = info.token = {}; - function update(type) { + function update(type, key, value) { if (info.token !== token) return; + info.resolved = key && { [key]: value }; + const child_ctx = assign(assign({}, info.ctx), info.resolved); const block = type && (info.current = type)(info.component, child_ctx); @@ -23,21 +25,9 @@ export function handlePromise(promise, info, mount, anchor) { if (isPromise(promise)) { promise.then(value => { - if (info.value) { - info.resolved = { [info.value]: value }; - update(info.then); - } else { - info.resolved = null; - update(null); - } + update(info.then, info.value, value); }, error => { - if (info.error) { - info.resolved = { [info.error]: error }; - update(info.catch); - } else { - info.resolved = null; - update(null); - } + update(info.catch, info.error, error); }); // if we previously had a then/catch block, destroy it @@ -46,10 +36,11 @@ export function handlePromise(promise, info, mount, anchor) { return true; } } else { - info.resolved = { [info.value]: promise }; if (info.current !== info.then) { - update(info.then); + update(info.then, info.value, promise); return true; } + + info.resolved = { [info.value]: promise }; } } \ No newline at end of file From 6437d7bfa779499a1d545afe958be8e616f6e97d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 5 May 2018 13:38:54 -0400 Subject: [PATCH 5/6] failing test for #1417 --- .../await-then-catch-static/_config.js | 48 +++++++++++++++++++ .../samples/await-then-catch-static/main.html | 7 +++ 2 files changed, 55 insertions(+) create mode 100644 test/runtime/samples/await-then-catch-static/_config.js create mode 100644 test/runtime/samples/await-then-catch-static/main.html diff --git a/test/runtime/samples/await-then-catch-static/_config.js b/test/runtime/samples/await-then-catch-static/_config.js new file mode 100644 index 0000000000..8029737d27 --- /dev/null +++ b/test/runtime/samples/await-then-catch-static/_config.js @@ -0,0 +1,48 @@ +let fulfil; + +let promise = new Promise(f => { + fulfil = f; +}); + +export default { + solo: 1, + data: { + promise + }, + + html: ` +

loading...

+ `, + + test(assert, component, target) { + fulfil(42); + + return promise + .then(() => { + assert.htmlEqual(target.innerHTML, ` +

loaded

+ `); + + promise = new Promise((f, r) => { + fulfil = f; + }); + + component.set({ + promise + }); + + assert.htmlEqual(target.innerHTML, ` +

loading...

+ `); + + fulfil(43); + + return promise.then(() => {}); + }) + .then(() => { + assert.htmlEqual(target.innerHTML, ` +

loaded

+ `); + }); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/await-then-catch-static/main.html b/test/runtime/samples/await-then-catch-static/main.html new file mode 100644 index 0000000000..b420fe969c --- /dev/null +++ b/test/runtime/samples/await-then-catch-static/main.html @@ -0,0 +1,7 @@ +{#await promise} +

loading...

+{:then value} +

loaded

+{:catch error} +

errored

+{/await} \ No newline at end of file From 8d772b163b51a627e027d7b616a64e3a8578669f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 5 May 2018 13:54:20 -0400 Subject: [PATCH 6/6] fix #1417 --- src/compile/nodes/AwaitBlock.ts | 9 +++------ test/runtime/samples/await-then-catch-static/_config.js | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts index 499e848436..19d863ccda 100644 --- a/src/compile/nodes/AwaitBlock.ts +++ b/src/compile/nodes/AwaitBlock.ts @@ -140,7 +140,7 @@ export default class AwaitBlock extends Node { ); block.builders.update.addLine( - `${info}.ctx = ctx` + `${info}.ctx = ctx;` ); if (this.pending.block.hasUpdateMethod) { @@ -153,10 +153,7 @@ export default class AwaitBlock extends Node { `); } else { block.builders.update.addBlock(deindent` - if (${conditions.join(' && ')}) { - ${info}.block.c(); - ${info}.block.m(${anchor}.parentNode, ${anchor}); - } + ${conditions.join(' && ')} `); } @@ -171,7 +168,7 @@ export default class AwaitBlock extends Node { [this.pending, this.then, this.catch].forEach(status => { status.children.forEach(child => { - child.build(status.block, null,'nodes'); + child.build(status.block, null, 'nodes'); }); }); } diff --git a/test/runtime/samples/await-then-catch-static/_config.js b/test/runtime/samples/await-then-catch-static/_config.js index 8029737d27..e718916f62 100644 --- a/test/runtime/samples/await-then-catch-static/_config.js +++ b/test/runtime/samples/await-then-catch-static/_config.js @@ -5,7 +5,6 @@ let promise = new Promise(f => { }); export default { - solo: 1, data: { promise },