Skip to content

Async

Tests in this category ensure asynchronous (promise returning) methods can be called and overridden by the host.

Host overrides of asynchronous methods are invoked by the kernel

Test: asyncMethodCanBeOverridden

When the host subclasses a jsii class and overrides a promise-returning method, invoking another asynchronous method on the instance that awaits the overridden one MUST cause the kernel to call back into the host's override, and the value the host returns MUST be used as the awaited result. A method the host adds that does not override any base member MUST NOT be registered as an override, but the host MAY still call it from within its override.

Reference Implementation

// GIVEN
export class AsyncVirtualMethods {
  public async callMe() {
    return (await this.overrideMe(10)) + this.dontOverrideMe() + (await this.overrideMeToo());
  }
  public async overrideMe(mult: number) {
    return Promise.resolve(12 * mult);
  }
  public async overrideMeToo() {
    return Promise.resolve(0);
  }
  public dontOverrideMe() {
    return 8;
  }
}

// WHEN
class OverrideAsyncMethods extends AsyncVirtualMethods {
  public async overrideMe(_mult: number) {
    return this.foo() * 2;
  }
  // Does not override any base member.
  public foo() {
    return 2222;
  }
}

const obj = new OverrideAsyncMethods();

// THEN
expect(await obj.callMe()).toBe(4452);

Asynchronous methods returning no value can be invoked

Test: asyncMethodReturningNothing

The host MUST be able to invoke a promise-returning method that resolves to no value, both when it is a static method and when it is an instance method. In each case the host MUST issue the invocation as an asynchronous request (sbegin for the static method, begin for the instance method), drive it to completion, and observe successful completion with no value returned.

Reference Implementation

// GIVEN
export class PromiseNothing {
  public static async promiseIt(): Promise<void> {
    return Promise.resolve();
  }

  public async instancePromiseIt(): Promise<void> {
    return PromiseNothing.promiseIt();
  }
}

// WHEN / THEN
await expect(new PromiseNothing().instancePromiseIt()).resolves.toBeUndefined();
await expect(PromiseNothing.promiseIt()).resolves.toBeUndefined();

Asynchronous methods can be invoked from the host

Test: asyncMethodsCanBeCalled

The host MUST be able to invoke a promise-returning (asynchronous) method on an object reference. It MUST issue the call as an asynchronous (begin) request, allow the kernel to run its pending callbacks and promises to completion, and then collect the resolved value, which it MUST return to the caller. This applies both to a method that internally awaits other asynchronous methods and to an asynchronous method invoked directly.

Reference Implementation

// GIVEN
export class AsyncVirtualMethods {
  public async callMe() {
    return (await this.overrideMe(10)) + this.dontOverrideMe() + (await this.overrideMeToo());
  }
  public async overrideMe(mult: number) {
    return Promise.resolve(12 * mult);
  }
  public async overrideMeToo() {
    return Promise.resolve(0);
  }
  public dontOverrideMe() {
    return 8;
  }
}

// WHEN
const obj = new AsyncVirtualMethods();

// THEN
expect(await obj.callMe()).toBe(128);
expect(await obj.overrideMe(44)).toBe(528);

Overrides inherited from a host base class are registered

Test: asyncOverrideCanBeInherited

When the host instantiates a class that inherits an asynchronous-method override from one of its own (host-defined) base classes, that override MUST still be registered with the kernel. Invoking a method that awaits the overridden method MUST cause the kernel to call back into the inherited host implementation.

Reference Implementation

// GIVEN
export class AsyncVirtualMethods {
  public async callMe() {
    return (await this.overrideMe(10)) + this.dontOverrideMe() + (await this.overrideMeToo());
  }
  public async overrideMe(mult: number) {
    return Promise.resolve(12 * mult);
  }
  public async overrideMeToo() {
    return Promise.resolve(0);
  }
  public dontOverrideMe() {
    return 8;
  }
}

// WHEN
class OverrideAsyncMethods extends AsyncVirtualMethods {
  public async overrideMe(_mult: number) {
    return this.foo() * 2;
  }
  public foo() {
    return 2222;
  }
}

// The override is inherited, not declared directly on the instantiated class.
class OverrideAsyncMethodsByBaseClass extends OverrideAsyncMethods {}

const obj = new OverrideAsyncMethodsByBaseClass();

// THEN
expect(await obj.callMe()).toBe(4452);

A host asynchronous override can call the base implementation

Test: asyncOverrideCanCallSuper

When a host override of a promise-returning method invokes the base class implementation, the host MUST be able to call back into the kernel to run the original method and MUST receive its resolved value. The override MAY combine that value with its own logic, and the combined result MUST be used as the method's result.

Reference Implementation

// GIVEN
export class AsyncVirtualMethods {
  public async callMe() {
    return (await this.overrideMe(10)) + this.dontOverrideMe() + (await this.overrideMeToo());
  }
  public async overrideMe(mult: number) {
    return Promise.resolve(12 * mult);
  }
  public async overrideMeToo() {
    return Promise.resolve(0);
  }
  public dontOverrideMe() {
    return 8;
  }
}

// WHEN
class OverrideCallsSuper extends AsyncVirtualMethods {
  public async overrideMe(mult: number) {
    const superValue = await super.overrideMe(mult);
    return superValue * 10 + 1;
  }
}

const obj = new OverrideCallsSuper();

// THEN
expect(await obj.overrideMe(12)).toBe(1441);
expect(await obj.callMe()).toBe(1209);

Errors thrown by host asynchronous overrides propagate to the caller

Test: asyncOverrideErrorPropagates

When the kernel calls back into a host override of a promise-returning method and that override throws, the host MUST transport the failure to the kernel. The awaiting kernel method MUST then reject, and the host that invoked the asynchronous method MUST observe an error rather than a result. The original error's message MUST be preserved.

Reference Implementation

// GIVEN
export class AsyncVirtualMethods {
  public async callMe() {
    return (await this.overrideMe(10)) + this.dontOverrideMe() + (await this.overrideMeToo());
  }
  public async overrideMe(mult: number) {
    return Promise.resolve(12 * mult);
  }
  public async overrideMeToo() {
    return Promise.resolve(0);
  }
  public dontOverrideMe() {
    return 8;
  }
}

// WHEN
class Throwing extends AsyncVirtualMethods {
  public async overrideMe(_mult: number): Promise<number> {
    throw new Error('Thrown by native code');
  }
}

const obj = new Throwing();

// THEN
await expect(obj.callMe()).rejects.toThrow('Thrown by native code');

Multiple asynchronous methods can be overridden at once

Test: multipleAsyncMethodsCanBeOverridden

When the host overrides more than one promise-returning method of a class, invoking a method that awaits both overridden methods MUST cause the kernel to call back into each of the host's overrides, and MUST compose their resolved values into the final result.

Reference Implementation

// GIVEN
export class AsyncVirtualMethods {
  public async callMe() {
    return (await this.overrideMe(10)) + this.dontOverrideMe() + (await this.overrideMeToo());
  }
  public async overrideMe(mult: number) {
    return Promise.resolve(12 * mult);
  }
  public async overrideMeToo() {
    return Promise.resolve(0);
  }
  public dontOverrideMe() {
    return 8;
  }
}

// WHEN
class TwoOverrides extends AsyncVirtualMethods {
  public async overrideMe(_mult: number) {
    return 666;
  }
  public async overrideMeToo() {
    return 10;
  }
}

const obj = new TwoOverrides();

// THEN
expect(await obj.callMe()).toBe(684);

Static asynchronous methods can be invoked from the host

Test: staticAsyncMethodsCanBeCalled

The host MUST be able to invoke a promise-returning (asynchronous) static method without an instance. It MUST issue the call as a static asynchronous (sbegin) request with the method's arguments, allow the kernel to run its pending callbacks and promises to completion, and then collect the resolved value, which it MUST return to the caller with its declared type.

Reference Implementation

// GIVEN
export class StaticAsyncMethods {
  public static async addOne(value: number): Promise<number> {
    return Promise.resolve(value + 1);
  }

  private constructor() {}
}

// WHEN / THEN
expect(await StaticAsyncMethods.addOne(41)).toBe(42);

Kernel Trace

> {"api":"sbegin","fqn":"jsii-calc.StaticAsyncMethods","method":"addOne","args":[41]}
< {"ok":{"promiseid":"jsii::promise::20000"}}
> {"api":"callbacks"}
< {"ok":{"callbacks":[]}}
> {"api":"end","promiseid":"jsii::promise::20000"}
< {"ok":{"result":42}}