From 5be750b424f9a8e293262f183363cde8b2b06b21 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilev Date: Fri, 23 Dec 2022 02:14:38 +0800 Subject: [PATCH] WIP --- src/editor/calltree.js | 13 +++++- src/editor/value_explorer.js | 6 +-- src/eval.js | 4 +- src/patch_promise.js | 91 ++++++++++-------------------------- test/self_hosted_test.js | 3 ++ test/test.js | 19 ++++++-- 6 files changed, 61 insertions(+), 75 deletions(-) diff --git a/src/editor/calltree.js b/src/editor/calltree.js index abc4ad2..eba7f89 100644 --- a/src/editor/calltree.js +++ b/src/editor/calltree.js @@ -13,6 +13,17 @@ const join = arr => arr.reduce( [], ) +const is_error = n => + !n.ok + || + ( + n.value instanceof globalThis.run_window.Promise + && + n.value.status != null + && + !n.value.status.ok + ) + export class CallTree { constructor(ui, container) { this.ui = ui @@ -108,7 +119,7 @@ export class CallTree { ) : el('span', 'call_header ' - + (n.ok ? '' : 'error') + + (is_error(n) ? 'error' : '') + (n.fn.__location == null ? ' native' : '') , // TODO show `this` argument diff --git a/src/editor/value_explorer.js b/src/editor/value_explorer.js index e145359..655795c 100644 --- a/src/editor/value_explorer.js +++ b/src/editor/value_explorer.js @@ -42,10 +42,10 @@ export const stringify_for_header = v => { if(v.status == null) { return `Promise` } else { - if(status.ok) { - return `Promise` + if(v.status.ok) { + return `Promise` } else { - return `Promise` + return `Promise` } } } else if(isError(v)) { diff --git a/src/eval.js b/src/eval.js index 2f7a4f7..d558da1 100644 --- a/src/eval.js +++ b/src/eval.js @@ -367,7 +367,7 @@ export const eval_modules = ( } const set_promise_status = value => { - if(value instanceof Promise.Original) { + if(value instanceof Promise) { // record stack for async calls, so expand calls works sync set_record_call() return value @@ -988,7 +988,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => { ok = true value = - expr.result.value } else if(node.operator == 'await') { - if(expr.result.value instanceof globalThis.run_window.Promise.Original) { + if(expr.result.value instanceof globalThis.run_window.Promise) { const status = expr.result.value.status if(status == null) { // Promise must be already resolved diff --git a/src/patch_promise.js b/src/patch_promise.js index 9b2e616..1c2c49b 100644 --- a/src/patch_promise.js +++ b/src/patch_promise.js @@ -1,78 +1,37 @@ export const patch_promise = window => { - if(window.Promise.Original != null) { + if(window.Promise.__patched) { // already patched return } - class PromiseRecordChildren extends Promise { - then(on_resolve, on_reject) { - let children = window.get_children() - if(children == null) { - children = [] - window.set_children(children) - } - return super.then( - on_resolve == null - ? null - : value => { - window.set_children(children) - return on_resolve(value) - }, + const _then = Promise.prototype.then - on_reject == null - ? null - : error => { - window.set_children(children) - return on_reject(error) + Promise.prototype.then = function then(on_resolve, on_reject) { + let children = window.get_children() + if(children == null) { + children = [] + window.set_children(children) + } + + const make_callback = cb => cb == null + ? null + : value => { + const current = window.get_children() + window.set_children(children) + try { + return cb(value) + } finally { + window.set_children(current) } - ) - } - } - - class PromiseWithStatus extends window.Promise { - constructor(fn) { - let status - let is_constructor_finished = false - const p = new PromiseRecordChildren( - (resolve, reject) => { - fn( - (value) => { - if(value instanceof window.Promise.Original) { - value - .then(v => { - p.status = {ok: true, value: v} - resolve(v) - }) - .catch(e => { - p.status = {ok: false, error: e} - reject(e) - }) - } else { - status = {ok: true, value} - if(is_constructor_finished) { - p.status = status - } - resolve(value) - } - }, - (error) => { - status = {ok: false, error} - if(is_constructor_finished) { - p.status = status - } - reject(error) - }, - ) } - ) - is_constructor_finished = true - p.status = status - return p - } + + return _then.call( + this, + make_callback(on_resolve), + make_callback(on_reject), + ) } - PromiseWithStatus.Original = window.Promise - - window.Promise = PromiseWithStatus + window.Promise.__patched = true } diff --git a/test/self_hosted_test.js b/test/self_hosted_test.js index b7be520..30b4a42 100644 --- a/test/self_hosted_test.js +++ b/test/self_hosted_test.js @@ -84,4 +84,7 @@ const run = root.children[0] assert_equal(root_calltree_node(state).ok, true) +// Assert that run children are tests +assert_equal(run.children.length > 100, true) + console.timeEnd('run') diff --git a/test/test.js b/test/test.js index 4fbdf8b..14eb14b 100644 --- a/test/test.js +++ b/test/test.js @@ -2754,7 +2754,7 @@ const y = x()` assert_equal(logs, [2, 1]) }), - test('async/await logs out of order', async () => { + test('async/await logs out of order timeout', async () => { const i = await test_initial_state_async(` const delay = async time => { await new Promise(res => globalThis.setTimeout(res, time*10)) @@ -2779,12 +2779,25 @@ const y = x()` ) }), + test('async/await then bug', async () => { + await assert_code_evals_to_async( + ` + const p2 = Promise.resolve(2) + const p1 = p2.then(() => 1) + const x = () => 1 + await x() + `, + 1 + ) + }), + test('async/await Promise.then creates subcall', async () => { const i = await test_initial_state_async(` const x = () => 1 await Promise.resolve(1).then(x) `) const root = root_calltree_node(i) + assert_equal(root.children.at(-1).fn.name, 'then') assert_equal(root.children.at(-1).children[0].fn.name, 'x') }), @@ -2794,11 +2807,11 @@ const y = x()` await Promise.reject(1).catch(x) `) const root = root_calltree_node(i) + assert_equal(root.children.at(-1).fn.name, 'catch') assert_equal(root.children.at(-1).children[0].fn.name, 'x') }), - - test_only('async/await native Promise.then creates subcall', async () => { + test('async/await native Promise.then creates subcall', async () => { const i = await test_initial_state_async(` const x = () => 1 const async_fn = async () => 1