Source code

Revision control

Copy as Markdown

Other Tools

// With enough hackery, stepping in and out of async functions can be made to
// work as users expect.
//
// This test exercises the common case when we have syntactically `await
// $ASYNC_FN($ARGS)` so that the calls nest as if they were synchronous
// calls. It works, but there's a problem.
//
// onStep fires in extra places that end users would find very confusing--see
// the comment marked (!) below. As a result, Debugger API consumers must do
// some extra work to skip pausing there. This test is a proof of concept that
// shows what sort of effort is needed. It maintains a single `asyncStack` and
// skips the onStep hook if we're not running the function at top of the async
// stack. Real debuggers would have to maintain multiple async stacks.
// Set up debuggee.
var g = newGlobal({newCompartment: true});
g.eval(`\
async function outer() { // line 1
return (await inner()) + (await inner()) + "!"; // 2
} // 3
async function inner() { // 4
return (await leaf()) + (await leaf()); // 5
} // 6
async function leaf() { // 7
return (await Promise.resolve("m")); // 8
} // 9
`);
// Set up debugger.
let previousLine = -1;
let dbg = new Debugger(g);
let log = "";
let asyncStack = [];
dbg.onEnterFrame = frame => {
assertEq(frame.type, "call");
// If we're entering this frame for the first time, push it to the async
// stack.
if (!frame.seen) {
frame.seen = true;
asyncStack.push(frame);
log += "(";
}
frame.onStep = () => {
// When stepping, we sometimes pause at opcodes in older frames (!)
// where all that's happening is async function administrivia.
//
// For example, the first time `leaf()` yields, `inner()` and
// `outer()` are still on the stack; they haven't awaited yet because
// control has not returned from `leaf()` to them yet. So stepping will
// hop from line 8 to line 5 to line 2 as we unwind the stack, then
// resume on line 8.
//
// Anyway: skip that noise.
if (frame !== asyncStack[asyncStack.length - 1])
return;
let line = frame.script.getOffsetLocation(frame.offset).lineNumber;
if (previousLine != line) {
log += line; // We stepped to a new line.
previousLine = line;
}
};
frame.onPop = completion => {
// Popping the frame. But async function frames are popped multiple
// times: for the "initial suspend", at each await, and on return. The
// debugger offers no easy way to distinguish them (bug 1470558).
// For now there's an "await" property, but bug 1470558 may come up
// with a different solution, so don't rely on it!
if (!completion.await) {
// Returning (not awaiting or at initial suspend).
assertEq(asyncStack.pop(), frame);
log += ")";
}
};
};
// Run.
let result;
g.outer().then(v => { result = v; });
drainJobQueue();
assertEq(result, "mmmm!");
assertEq(log, "(12(45(789)5(789)56)2(45(789)5(789)56)23)");