This commit is contained in:
Dmitry Vasilev
2022-12-23 02:14:38 +08:00
parent 4dbc7c26e2
commit 5be750b424
6 changed files with 61 additions and 75 deletions

View File

@@ -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 { export class CallTree {
constructor(ui, container) { constructor(ui, container) {
this.ui = ui this.ui = ui
@@ -108,7 +119,7 @@ export class CallTree {
) )
: el('span', : el('span',
'call_header ' 'call_header '
+ (n.ok ? '' : 'error') + (is_error(n) ? 'error' : '')
+ (n.fn.__location == null ? ' native' : '') + (n.fn.__location == null ? ' native' : '')
, ,
// TODO show `this` argument // TODO show `this` argument

View File

@@ -42,10 +42,10 @@ export const stringify_for_header = v => {
if(v.status == null) { if(v.status == null) {
return `Promise<pending>` return `Promise<pending>`
} else { } else {
if(status.ok) { if(v.status.ok) {
return `Promise<fulfilled: ${stringify_for_header(status.value)}>` return `Promise<fulfilled: ${stringify_for_header(v.status.value)}>`
} else { } else {
return `Promise<fulfilled: ${stringify_for_header(status.error)}>` return `Promise<rejected: ${stringify_for_header(v.status.error)}>`
} }
} }
} else if(isError(v)) { } else if(isError(v)) {

View File

@@ -367,7 +367,7 @@ export const eval_modules = (
} }
const set_promise_status = value => { const set_promise_status = value => {
if(value instanceof Promise.Original) { if(value instanceof Promise) {
// record stack for async calls, so expand calls works sync // record stack for async calls, so expand calls works sync
set_record_call() set_record_call()
return value return value
@@ -988,7 +988,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
ok = true ok = true
value = - expr.result.value value = - expr.result.value
} else if(node.operator == 'await') { } 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 const status = expr.result.value.status
if(status == null) { if(status == null) {
// Promise must be already resolved // Promise must be already resolved

View File

@@ -1,78 +1,37 @@
export const patch_promise = window => { export const patch_promise = window => {
if(window.Promise.Original != null) { if(window.Promise.__patched) {
// already patched // already patched
return return
} }
class PromiseRecordChildren extends Promise { const _then = Promise.prototype.then
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)
},
on_reject == null Promise.prototype.then = function then(on_resolve, on_reject) {
? null let children = window.get_children()
: error => { if(children == null) {
window.set_children(children) children = []
return on_reject(error) 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 return _then.call(
p.status = status this,
return p make_callback(on_resolve),
} make_callback(on_reject),
)
} }
PromiseWithStatus.Original = window.Promise window.Promise.__patched = true
window.Promise = PromiseWithStatus
} }

View File

@@ -84,4 +84,7 @@ const run = root.children[0]
assert_equal(root_calltree_node(state).ok, true) assert_equal(root_calltree_node(state).ok, true)
// Assert that run children are tests
assert_equal(run.children.length > 100, true)
console.timeEnd('run') console.timeEnd('run')

View File

@@ -2754,7 +2754,7 @@ const y = x()`
assert_equal(logs, [2, 1]) 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 i = await test_initial_state_async(`
const delay = async time => { const delay = async time => {
await new Promise(res => globalThis.setTimeout(res, time*10)) 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 () => { test('async/await Promise.then creates subcall', async () => {
const i = await test_initial_state_async(` const i = await test_initial_state_async(`
const x = () => 1 const x = () => 1
await Promise.resolve(1).then(x) await Promise.resolve(1).then(x)
`) `)
const root = root_calltree_node(i) 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') assert_equal(root.children.at(-1).children[0].fn.name, 'x')
}), }),
@@ -2794,11 +2807,11 @@ const y = x()`
await Promise.reject(1).catch(x) await Promise.reject(1).catch(x)
`) `)
const root = root_calltree_node(i) 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') assert_equal(root.children.at(-1).children[0].fn.name, 'x')
}), }),
test('async/await native Promise.then creates subcall', async () => {
test_only('async/await native Promise.then creates subcall', async () => {
const i = await test_initial_state_async(` const i = await test_initial_state_async(`
const x = () => 1 const x = () => 1
const async_fn = async () => 1 const async_fn = async () => 1