Stream: jco

Topic: Unexpected error when using async JS types/functions with im


view this post on Zulip ktz_alias (Mar 16 2026 at 06:19):

I ran into several unexpected behaviors while experimenting with jco async imports.
Some of them may be related, so I’m listing them below.

view this post on Zulip ktz_alias (Mar 16 2026 at 06:20):

  1. When importing a JS class as a WIT resource, the generated code in the trampoline appears to be incorrect.

Current generated code:

foo?._isHostProvided = true;
...

await foo(arg);

However, since foo is a method on the resource instance, it seems the host-provided check should instead refer to the constructor. Something like:

let hostProvided = false;
hostProvided = rsc0.constructor._isHostProvided;

...

await rsc0.foo(arg);

It looks like the generated code treats the method as a free function instead of a method on the resource instance.

view this post on Zulip ktz_alias (Mar 16 2026 at 06:21):

  1. When mapping a JS class as an async WIT resource using the --map option, _isHostProvided is not initialized.

In contrast, when using --instantiation, _isHostProvided is initialized correctly.

Even if issue (1) is fixed, _isHostProvided remains undefined for JS class.
As a result, the following branch is never taken:

if (hostProvided) {
  task.resolve([ret]);
  endCurrentTask(0, task.id());
  return task.completionPromise();
}

view this post on Zulip ktz_alias (Mar 16 2026 at 06:22):

  1. Async functions returning a result type cause a runtime error.

In _lowerFlatVariant, the following code is generated:

if (!variant.discriminant) {
  throw new Error(`missing/invalid discriminant for variant [${variant}]`);
}

However, when the value is ok, variant.discriminant === 0.
Since 0 is falsy in JavaScript, this condition incorrectly treats the value as invalid and throws the error.

It seems the check should instead verify that the discriminant is undefined or null, e.g.:

if ((variant.discriminant === undefined) || (variant.discriminant === null)) {
  throw new Error(`missing/invalid discriminant for variant [${variant}]`);
}

view this post on Zulip ktz_alias (Mar 16 2026 at 06:24):

  1. Calling host-provided async functions sequentially from Wasm results in an error on the second call.

For example:

async fn invoke() -> u32 {
    let v = my_intf::foo().await;
    my_intf::bar(v).await // error occurs here
}

When calling a function exported from Wasm, the first call (task) transitions to AsyncSubtask.State.STARTED due to the following code:

if (currentSubtask && currentSubtask.isNotStarted()) {
  currentSubtask.onStart();
}

However, for the second call, onStart is not invoked for the subtask, so it proceeds with #state == AsyncSubtask.State.STARTING.

As a result, the following check in AsyncSubtask.onResolved throws an error:

if (this.#state !== AsyncSubtask.State.STARTED) {
  throw new Error(
    "cancelled subtask must have been started before cancellation",
  );
}

As a workaround, calling subtask.onStart() inside the setTimeout handler registered by _lowerImport allows the call sequence to complete successfully.
This suggests that the subtask may still be in the STARTING state when the trampoline is invoked.

setTimeout(async () => {
  ...
  if (subtask.state() !== AsyncSubtask.State.STARTED) {
    subtask.onStart();
  }
  exportFn.apply(null, params);
  ...
});

view this post on Zulip ktz_alias (Mar 16 2026 at 06:24):

This appears to be related to https://github.com/bytecodealliance/jco/issues/1272.

view this post on Zulip Victor Adossi (Mar 17 2026 at 11:51):

Hey @ktz_alias thanks for the detailed bug report! Would you mind filing this at https://github.com/bytecodealliance/jco/issues ?

view this post on Zulip Victor Adossi (Mar 17 2026 at 11:55):

Also, if you could include which version of Jco you're using that would be great! I think the _isHostProvided issues persist but I'm not sure the others aren't already solved.

view this post on Zulip ktz_alias (Mar 18 2026 at 07:02):

@Victor Adossi Thanks!
I’ve already filed a related issue (#1272). I’ll update it with the additional findings and workaround.

view this post on Zulip ktz_alias (Mar 18 2026 at 07:02):

For the cases that seem to be separate issues (although they occur along the same flow), I’ll open new issues and link them for clarity.

view this post on Zulip ktz_alias (Mar 18 2026 at 09:50):

Followed up on GitHub with details and workarounds:

view this post on Zulip Victor Adossi (Mar 18 2026 at 10:00):

Thanks, that's great! Thanks for splitting them up as well, will try and get through them ASAP!

Working on fixing the struct lifting now, some work that was already underway to finish the async implementation is what you've run into, I think


Last updated: Mar 23 2026 at 16:19 UTC