Skip to content

Cannot catch top-level async/await error #953

@0c3d7r4

Description

@0c3d7r4

It's not possible to catch an error in top-level async/await

JsResult run(Source source) throws InterruptedException, ExecutionException {
  CompletableFuture<JsResult> finalResult = new CompletableFuture<>();

  // Submit everything to the JS event loop
  eventLoop.submit(() -> {
   try {
    Value result = context.eval(source); // Eval runs on JS thread
    String state = "pending";
    if (result.hasMember("state")) {
     state = result.invokeMember("state").asString();
    }

    if ("fulfilled".equals(state)) {
     finalResult.complete(JsResult.ok(result.invokeMember("value")));
    } else if ("rejected".equals(state)) {
     Value reason = result.invokeMember("reason");
     finalResult.completeExceptionally(
       new RuntimeException(reason.toString()));
    } else if (result != null && result.hasMember("then")) {
     // Promise: attach handlers
     result.invokeMember("then", (ProxyExecutable) args -> {
      // (2J) resolving fine
      finalResult.complete(JsResult.ok(args.length > 0 ? args[0] : null));
      return null;
     }, (ProxyExecutable) args -> {
      // (3J) not catching - bug???
      Value jsError = args.length > 0 ? args[0] : null;
      finalResult.complete(JsResult.error(jsError));
      return null;
     });
    } else {
     // Not a Promise, resolve immediately
     finalResult.complete(JsResult.ok(result));
    }
   }catch (Throwable t) {
    // (1J) Any immediate JS error
    finalResult.complete(JsResult.error(t));
   }
  });

  // Pump JS event loop until the result completes (or fails)
  eventLoop.runUntil(finalResult::isDone);

  // Stop the event loop now to avoid dangling tasks
  close();

  // Propagate exceptions to caller
  return finalResult.get();
 }

in js:

// throw new Error('(1S) this can be caught alright in (1J)') 

try {
 await Promise.all([
  read('test/fixture/index.js').then(()=>{
   console.log('[🥇 greel] read index.js')
  }),
  read('test1/fixture/hello-world.js').then(()=>{
   console.log('[🥇 greel] read hello-world.js')
  })
 ])
}catch(er) {
 console.log('[🥇 greel] ❌',er.message)
 throw er // <--- (2) RETHROW ERROR HERE, CAUSES BUG: NOT CAUGHT
}

console.log('[🥇 greel] 🏁 Completed program')

async function read(path) {
 var d=new Date
 // await new Promise(r=>setTimeout(r,Math.random()*500))
 console.log('[🥇 greel] Asking to read',path)
 var result=await fs.read(path)
 console.log(`[🥇 greel] ✅ read ${path} after ${(new Date).getTime()-d.getTime()}ms (${result.length}B)`)
}

outputs

[🥇 greel] Asking to read test/fixture/index.js
{🗿 graal} Starting read test/fixture/index.js
{🗿 graal} 📖 Completed read test/fixture/index.js
[🥇 greel] Asking to read test1/fixture/hello-world.js
{🗿 graal} Starting read test1/fixture/hello-world.js
{🗿 graal} 📕 Failed read test1/fixture/hello-world.js
[🥇 greel] ✅ read test/fixture/index.js after 2289ms (918B)
[🥇 greel] read index.js
[🥇 greel] ❌ Failed to read file: File not found: test1/fixture/hello-world.js

so it's stalling. if there's an error before await on the top-level (1S), it catches it in java's try-catch (1J), but if it happens after await, the second (catchy) callback to "then" (3J) it's not being executed.

when there's no error, it successfully lands in the callback for .then in Java (2J)

[🥇 greel] Asking to read test/fixture/index.js
{🗿 graal} Starting read test/fixture/index.js
{🗿 graal} 📖 Completed read test/fixture/index.js
[🥇 greel] Asking to read test/fixture/hello-world.js
{🗿 graal} Starting read test/fixture/hello-world.js
{🗿 graal} 📖 Completed read test/fixture/hello-world.js
[🥇 greel] ✅ read test/fixture/index.js after 78ms (928B)
[🥇 greel] read index.js
[🥇 greel] ✅ read test/fixture/hello-world.js after 46ms (51B)
[🥇 greel] read hello-world.js
[🥇 greel] 🏁 Completed program

seems like a bug to me, but maybe i haven't set up global error listeners or smth?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions