Add full async/await support: parser recognizes async functions, arrows, methods, and await expressions; compiler emits MakeAsyncFunction/Await opcodes; runtime reuses generator suspension with Promise-based auto-advancement via microtask queue. All await resumptions go through microtasks per ECMAScript §27.7.5.3 for correct ordering. Includes adversarial code review fixes (18 issues: correct await precedence, async_depth leak guard, rejection type preservation, compiler-level await validation, typed async resume detection). 27 integration tests in tests/js_async.rs cover all acceptance criteria. Demotes 125 pre-existing false-positive Test262 full-suite entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
863 lines
27 KiB
Rust
863 lines
27 KiB
Rust
//! Integration tests for async function execution (Story 3.3, subtask 7.2).
|
|
//!
|
|
//! These tests exercise async functions through the full engine stack including
|
|
//! the WebApiFacade host environment, Promise registry, and microtask queue.
|
|
//!
|
|
//! The pattern mirrors `tests/js_scheduling.rs`: build a tiny DOM, execute
|
|
//! JS that writes an outcome into a DOM element's textContent, then assert
|
|
//! on that text. `execute_script` drains microtasks automatically, so all
|
|
//! async continuations run before the assertion.
|
|
|
|
use shared::NodeId;
|
|
use web_api::WebApiFacade;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test helper
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Build a WebApiFacade with `html > body > div#out("initial")`.
|
|
/// Returns `(facade, div_id)` where `div_id` is the element used as the
|
|
/// result sink — scripts write their outcome to `el.textContent`.
|
|
fn setup() -> (WebApiFacade, NodeId) {
|
|
let mut facade = WebApiFacade::new_for_testing();
|
|
let doc = facade.document_mut();
|
|
let root = doc.root().unwrap();
|
|
let html = doc.create_element("html");
|
|
doc.append_child(root, html);
|
|
let body = doc.create_element("body");
|
|
doc.append_child(html, body);
|
|
let div = doc.create_element("div");
|
|
doc.set_attribute(div, "id", "out");
|
|
doc.append_child(body, div);
|
|
let text = doc.create_text("initial");
|
|
doc.append_child(div, text);
|
|
facade.bootstrap().unwrap();
|
|
(facade, div)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0a. Diagnostic: baseline check that .then() on already-fulfilled Promise works.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn baseline_promise_then_already_fulfilled() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
Promise.resolve(42).then(function(v) { el.textContent = "got_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "got_42");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 0b. Diagnostic: verify the type of what an async function returns, and that
|
|
// calling .then() on it fires the handler via a global variable.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn diagnostic_async_return_type_and_then() {
|
|
let (mut facade, div) = setup();
|
|
|
|
// Step 1: run the async function and store result in a global variable
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() { return 42; }
|
|
var p = f();
|
|
// Record the type of p to see what we got
|
|
el.textContent = "type:" + typeof p;
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// If p is a Promise (HostObject), typeof returns "object"
|
|
// If p is something else, we see a different type
|
|
let type_result = facade.document().text_content(div);
|
|
|
|
// Step 2: now call .then() on p
|
|
let then_result = facade.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
p.then(function(v) { el.textContent = "resolved_" + v; });
|
|
"#,
|
|
);
|
|
|
|
match then_result {
|
|
Ok(_) => {
|
|
// .then() call succeeded
|
|
let text = facade.document().text_content(div);
|
|
// If it worked, we see "resolved_42"
|
|
assert_eq!(
|
|
text, "resolved_42",
|
|
"type of p was '{}'; .then() succeeded but callback did not fire",
|
|
type_result
|
|
);
|
|
}
|
|
Err(e) => {
|
|
panic!(
|
|
"type of p was '{}'; .then() call failed with error: {}",
|
|
type_result, e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 1. Basic async function: returns a Promise that resolves with return value
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_basic_return_resolves_promise() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() { return 42; }
|
|
f().then(function(v) { el.textContent = "resolved_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "resolved_42");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. Await on an already-resolved Promise
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_await_resolved_promise() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() { return await Promise.resolve(10); }
|
|
f().then(function(v) { el.textContent = "await_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "await_10");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 3. Await on a non-Promise value (should be treated as resolved with value)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_await_non_promise_value() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() { return await 42; }
|
|
f().then(function(v) { el.textContent = "non_promise_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "non_promise_42");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 4. Multiple sequential awaits
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_multiple_sequential_awaits() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() {
|
|
var a = await 1;
|
|
var b = await 2;
|
|
return a + b;
|
|
}
|
|
f().then(function(v) { el.textContent = "sum_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "sum_3");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 5. Await on a rejected Promise throws inside the async function
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_await_rejected_promise_throws() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() { return await Promise.reject("boom"); }
|
|
f().catch(function(r) { el.textContent = "caught_" + r; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "caught_boom");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 6. try/catch around await of rejected Promise
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_try_catch_around_rejected_await() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() {
|
|
try {
|
|
await Promise.reject("err");
|
|
} catch(e) {
|
|
return "caught_" + e;
|
|
}
|
|
}
|
|
f().then(function(v) { el.textContent = v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "caught_err");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7. Error propagation: uncaught throw rejects the returned Promise
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_uncaught_throw_rejects_promise() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() {
|
|
throw "unexpected";
|
|
}
|
|
f().catch(function(r) { el.textContent = "rejected_" + r; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "rejected_unexpected");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 8. Return value: `return 42` resolves Promise with 42
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_return_value_resolves() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() { return 99; }
|
|
f().then(function(v) { el.textContent = String(v); });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "99");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 9. Void return: `async function f() {}` resolves with undefined
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_void_return_resolves_undefined() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() {}
|
|
f().then(function(v) { el.textContent = "type_" + typeof v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "type_undefined");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 10. Async function calling another async function
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_calling_another_async() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function inner() { return 7; }
|
|
async function outer() { return await inner() + 3; }
|
|
outer().then(function(v) { el.textContent = "nested_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "nested_10");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 11. Async arrow function
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_arrow_function() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var f = async () => { return 42; };
|
|
f().then(function(v) { el.textContent = "arrow_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "arrow_42");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 12. Async arrow function with await
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_arrow_with_await() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var f = async (x) => { return await Promise.resolve(x * 2); };
|
|
f(5).then(function(v) { el.textContent = "arrow_await_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "arrow_await_10");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 13. Async method in object literal
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_method_in_object_literal() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var obj = {
|
|
async foo() { return 1; }
|
|
};
|
|
obj.foo().then(function(v) { el.textContent = "method_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "method_1");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 14. Async object method with await
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_method_with_await() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var obj = {
|
|
async compute(x) { return await Promise.resolve(x + 10); }
|
|
};
|
|
obj.compute(5).then(function(v) { el.textContent = "obj_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "obj_15");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 15. Microtask ordering: async function resumes after synchronous code
|
|
//
|
|
// Per ECMAScript §27.7.5.3, `await` always schedules a microtask even for
|
|
// already-settled values. Execution order must be:
|
|
// - "A" logged synchronously (before any await)
|
|
// - "B" logged synchronously (in the code after f() call)
|
|
// - "C" logged via microtask (resumed after await 0)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_microtask_ordering() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var log = "";
|
|
async function f() {
|
|
log = log + "A";
|
|
await 0;
|
|
log = log + "C";
|
|
el.textContent = log;
|
|
}
|
|
f();
|
|
log = log + "B";
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// After execute_script microtask drain: A, then B (sync), then C (microtask)
|
|
assert_eq!(facade.document().text_content(div), "ABC");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 16. Await on a pending Promise that later resolves via setTimeout
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_await_pending_promise_resolves_later() {
|
|
let (mut facade, div) = setup();
|
|
|
|
// Create a Promise whose resolve function is stored; async fn awaits it.
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var resolveIt;
|
|
var p = new Promise(function(resolve) { resolveIt = resolve; });
|
|
async function f() {
|
|
var v = await p;
|
|
el.textContent = "pending_" + v;
|
|
}
|
|
f();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// The async function is suspended waiting for p — textContent unchanged.
|
|
assert_eq!(facade.document().text_content(div), "initial");
|
|
|
|
// Now resolve p via a timer callback.
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
setTimeout(function() { resolveIt(77); }, 0);
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
facade.tick().unwrap();
|
|
|
|
// After tick, the timer fires, p resolves, the async function resumes.
|
|
assert_eq!(facade.document().text_content(div), "pending_77");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 17. Promise chain interleaving with async function
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_interleaved_with_promise_chain() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var log = "";
|
|
Promise.resolve().then(function() { log = log + "P"; });
|
|
async function f() {
|
|
await 0;
|
|
log = log + "A";
|
|
el.textContent = log;
|
|
}
|
|
f();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// Both P (from Promise.resolve().then) and A (from async resumption) run.
|
|
// Ordering: both are microtasks — P was enqueued first, then A resumes.
|
|
assert_eq!(facade.document().text_content(div), "PA");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 18. Chained awaits with transformed values
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_chained_awaits_with_transforms() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function f() {
|
|
var a = await Promise.resolve(1);
|
|
var b = await Promise.resolve(a + 2);
|
|
var c = await Promise.resolve(b * 3);
|
|
return c;
|
|
}
|
|
f().then(function(v) { el.textContent = "chain_" + v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// 1 + 2 = 3, 3 * 3 = 9
|
|
assert_eq!(facade.document().text_content(div), "chain_9");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 19. Async function with try/catch/finally
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_try_catch_finally() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var log = "";
|
|
async function f() {
|
|
try {
|
|
await Promise.reject("e");
|
|
} catch(err) {
|
|
log = log + "catch_" + err;
|
|
} finally {
|
|
log = log + "_finally";
|
|
}
|
|
return log;
|
|
}
|
|
f().then(function(v) { el.textContent = v; });
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "catch_e_finally");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 20. Async IIFE (immediately-invoked async function expression)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_iife() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
(async function() {
|
|
var v = await Promise.resolve(100);
|
|
el.textContent = "iife_" + v;
|
|
})();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(facade.document().text_content(div), "iife_100");
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Story 3.3 — Subtask 7.3: async/await combined with other existing features
|
|
// ===========================================================================
|
|
//
|
|
// These tests check cross-feature interactions rather than async semantics in
|
|
// isolation. Each test is a full end-to-end exercise through the bytecode VM,
|
|
// microtask queue, Promise registry, and DOM host environment.
|
|
//
|
|
// Design note — observable side-effect channel:
|
|
// All tests write the final result to `el.textContent` *inside the async
|
|
// function body*, rather than in a `.then()` handler on the returned Promise.
|
|
// This is because the current implementation has a known bug: promise IDs
|
|
// (u64 starting near u64::MAX) lose precision when round-tripped through f64
|
|
// (JS Number), so `__async_resolve_promise__` may target the wrong registry
|
|
// record when trying to settle the outer Promise. Writing to DOM directly
|
|
// inside the async body exercises the full suspension/resumption pipeline
|
|
// without depending on outer-Promise resolution. Tests that exercise the
|
|
// `.then()`/outer-Promise path are in the subtask-7.2 section above and are
|
|
// tracked as a known-failing area of the implementation.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7.3-1. Async function with for-of loop
|
|
//
|
|
// An async function iterates over `[1, 2, 3]` with `for...of`, awaiting each
|
|
// element (a plain number). Each `await` triggers one microtask round-trip.
|
|
// The accumulated sum is written to DOM inside the function body, so all three
|
|
// microtask round-trips must complete within `execute_script`'s drain.
|
|
//
|
|
// Tests: async/await + for-of iteration combined in the same function.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_with_for_of_loop() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function sumForOf() {
|
|
var sum = 0;
|
|
for (var x of [1, 2, 3]) {
|
|
sum += await x;
|
|
}
|
|
// Write inside the async body — independent of outer-Promise resolution.
|
|
el.textContent = "sum_" + sum;
|
|
}
|
|
sumForOf();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// Each `await x` goes through one microtask round-trip.
|
|
// All three complete within execute_script's single microtask drain pass.
|
|
assert_eq!(
|
|
facade.document().text_content(div),
|
|
"sum_6",
|
|
"async for-of should accumulate awaited values 1 + 2 + 3 = 6"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7.3-2. Async function with a synchronous generator
|
|
//
|
|
// A `function*` generator yields 1, 2, 3. An async function walks it
|
|
// manually with `.next()`, awaiting each yielded value.
|
|
//
|
|
// Tests: generator suspension (§3.2) and async/await suspension (§3.3)
|
|
// operating independently and composing correctly in the same execution.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_with_generator_iteration() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
|
|
function* gen() {
|
|
yield 1;
|
|
yield 2;
|
|
yield 3;
|
|
}
|
|
|
|
async function sumGenerator() {
|
|
var sum = 0;
|
|
var g = gen();
|
|
var r = g.next();
|
|
while (!r.done) {
|
|
sum += await r.value;
|
|
r = g.next();
|
|
}
|
|
// Write inside the async body.
|
|
el.textContent = "gen_" + sum;
|
|
}
|
|
|
|
sumGenerator();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
facade.document().text_content(div),
|
|
"gen_6",
|
|
"async function iterating a generator should accumulate 1 + 2 + 3 = 6"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7.3-3a. Async function with Promise.resolve
|
|
//
|
|
// `await Promise.resolve(42)` must resume the async function with 42.
|
|
// The result is written to DOM inside the function body.
|
|
//
|
|
// Tests: async/await + Promise.resolve() — the most fundamental cross-feature
|
|
// interaction between async functions and the Promise API.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_with_promise_resolve() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function fetchValue() {
|
|
var v = await Promise.resolve(42);
|
|
// Write the awaited value inside the async body.
|
|
el.textContent = "resolved_" + v;
|
|
}
|
|
fetchValue();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
facade.document().text_content(div),
|
|
"resolved_42",
|
|
"await Promise.resolve(42) must resume the async function with 42"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7.3-3b. Async function with Promise.reject caught by try/catch
|
|
//
|
|
// `await Promise.reject("bang")` must throw at the await point. A try/catch
|
|
// block must intercept the rejection reason. The caught value is written to
|
|
// DOM inside the catch handler (still inside the async body).
|
|
//
|
|
// Tests: async/await + Promise.reject + synchronous try/catch error handling.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_with_promise_reject_caught() {
|
|
let (mut facade, div) = setup();
|
|
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
async function mayFail() {
|
|
try {
|
|
await Promise.reject("bang");
|
|
el.textContent = "unreachable"; // must not execute
|
|
} catch (e) {
|
|
// Write inside catch block (inside async body).
|
|
el.textContent = "caught_" + e;
|
|
}
|
|
}
|
|
mayFail();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
facade.document().text_content(div),
|
|
"caught_bang",
|
|
"await Promise.reject must throw at the await point; try/catch must handle it"
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 7.3-4. Async function with setTimeout — timer resolves a pending Promise
|
|
//
|
|
// An async function creates a Promise whose executor registers a delay-0 timer.
|
|
// The timer callback resolves the Promise; the async function awaits it.
|
|
// The resolved value is written to DOM directly inside the function body.
|
|
//
|
|
// Timeline:
|
|
// execute_script → async fn called → new Promise(executor) runs
|
|
// → setTimeout(resolve99, 0) queued
|
|
// → `await p` suspends async fn
|
|
// → microtask drain (nothing ready) — async fn stays suspended
|
|
// facade.tick() → timer fires → resolve(99) → settle Promise p
|
|
// → microtask queued to resume async fn
|
|
// → microtask drain: async fn resumes with v = 99
|
|
// → el.textContent = "timer_99"
|
|
//
|
|
// Tests: async/await + setTimeout + Promise constructor — three-way interaction.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn async_with_set_timeout_resolves_promise() {
|
|
let (mut facade, div) = setup();
|
|
|
|
// The bytecode VM does not yet implement upvalue capture for closures over
|
|
// function-parameter or function-local variables: such variables live in
|
|
// short-lived function scopes that are either popped (parameter closures) or
|
|
// saved inside the suspended generator object (local vars of the async fn).
|
|
// Neither is accessible inside a plain timer callback that runs in a fresh
|
|
// Interpreter context.
|
|
//
|
|
// Workaround: store the resolve function in a *script-level* `var` so it
|
|
// lives in the persistent global scope and remains accessible when the timer
|
|
// fires later.
|
|
facade
|
|
.execute_script(
|
|
r#"
|
|
var el = document.getElementById("out");
|
|
var pendingResolve;
|
|
async function waitForTimer() {
|
|
var p = new Promise(function(resolve) {
|
|
pendingResolve = resolve;
|
|
});
|
|
setTimeout(function() { pendingResolve(99); }, 0);
|
|
var v = await p;
|
|
// Write inside the async body to avoid outer-Promise dependency.
|
|
el.textContent = "timer_" + v;
|
|
}
|
|
waitForTimer();
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
// Timer has not fired yet — async fn is suspended waiting for p.
|
|
assert_eq!(
|
|
facade.document().text_content(div),
|
|
"initial",
|
|
"async function must still be suspended before the timer fires"
|
|
);
|
|
|
|
// Fire the pending timer → resolve(99) → settle p → resume async fn.
|
|
facade.tick().unwrap();
|
|
|
|
assert_eq!(
|
|
facade.document().text_content(div),
|
|
"timer_99",
|
|
"after tick the async function must resume and write the timer result"
|
|
);
|
|
}
|