diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index 5c48d8ba003e..1725e598c74e 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; diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts index 60ce1ad23d48..19d863ccdaef 100644 --- a/src/compile/nodes/AwaitBlock.ts +++ b/src/compile/nodes/AwaitBlock.ts @@ -77,94 +77,45 @@ 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`); - 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; + const infoProps = [ + block.alias('component') === 'component' ? 'component' : `component: #component`, + 'ctx', + 'current: 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}`, + this.then.block.name && `value: '${this.value}'`, + this.catch.block.name && `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 +123,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 +136,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,30 +148,27 @@ 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}); - } + ${conditions.join(' && ')} `); } 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(); + ${info} = null; `); [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/src/shared/await-block.js b/src/shared/await-block.js new file mode 100644 index 000000000000..c216f1e92aad --- /dev/null +++ b/src/shared/await-block.js @@ -0,0 +1,46 @@ +import { assign, isPromise } from './utils.js'; + +export function handlePromise(promise, info) { + var token = info.token = {}; + + 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); + + 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 => { + update(info.then, info.value, value); + }, error => { + update(info.catch, info.error, error); + }); + + // if we previously had a then/catch block, destroy it + if (info.current !== info.pending) { + update(info.pending); + return true; + } + } else { + if (info.current !== info.then) { + update(info.then, info.value, promise); + return true; + } + + info.resolved = { [info.value]: promise }; + } +} \ No newline at end of file diff --git a/src/shared/index.js b/src/shared/index.js index 774849f02772..a2b1ba357647 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 45260d49bc41..f9fbf80e66a9 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 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 000000000000..e718916f6201 --- /dev/null +++ b/test/runtime/samples/await-then-catch-static/_config.js @@ -0,0 +1,47 @@ +let fulfil; + +let promise = new Promise(f => { + fulfil = f; +}); + +export default { + 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 000000000000..b420fe969cc8 --- /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