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}}