Skip to content

await does not respect this types in .then methodΒ #47711

Closed
@jmartinezmaes

Description

@jmartinezmaes

Bug Report

πŸ”Ž Search Terms

promise, async, await, then, method, this, type

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Promises and async/await.

⏯ Playground Link

Link

πŸ’» Code

type Either<E, A> = Left<E> | Right<A>;
type Left<E> = { tag: 'Left', e: E };
type Right<A> = { tag: 'Right', a: A };

const mkLeft = <E>(e: E): Either<E, never> => ({ tag: 'Left', e });
const mkRight = <A>(a: A): Either<never, A> => ({ tag: 'Right', a });

// I tried omitting the `implements` cause here but the behavior did not change.
class EPromise<E, A> implements PromiseLike<A> {
    static succeed<A>(a: A): EPromise<never, A> {
        return new EPromise(Promise.resolve(mkRight(a)));
    }

    static fail<E>(e: E): EPromise<E, never> {
        return new EPromise(Promise.resolve(mkLeft(e)));
    }

    constructor(readonly p: PromiseLike<Either<E, A>>) { }

    then<B = A, B1 = never>(
        // EPromise can act as a Thenable only when `E` is `never`.
        this: EPromise<never, A>,
        onfulfilled?: ((value: A) => B | PromiseLike<B>) | null | undefined,
        onrejected?: ((reason: any) => B1 | PromiseLike<B1>) | null | undefined
    ): PromiseLike<B | B1> {
        return this.p.then(
            // Casting to `Right<A>` is safe here because we've eliminated the possibility of `Left<E>`.
            either => onfulfilled?.((either as Right<A>).a) ?? (either as Right<A>).a as unknown as B,
            onrejected
        )
    }
}

const withTypedFailure: EPromise<number, string> = EPromise.fail(1);

// Errors as expected:
//
// "The 'this' context of type 'EPromise<number, string>' is not assignable to method's
//     'this' of type 'EPromise<never, string>"
withTypedFailure.then(s => s.toUpperCase()).then(console.log);

async function test() {
    // Does not produce an equivalent error.
    // We're attempting to access property `a` of a `Right<string>` but in reality we have a `Left<number>`,
    // meaning that `str` here is `undefined`.
    const str = await withTypedFailure;

    // This will now cause a runtime error.
    return str.toUpperCase();
}

test()
    .then(console.log)
    .catch(console.error) // [ERR]: Cannot read properties of undefined (reading 'toUpperCase')

πŸ™ Actual behavior

Awaiting a Thenable with a user-defined this type in the .then method does not respect the invariants imposed by the this type.

πŸ™‚ Expected behavior

Awaiting a Thenable with a user-defined this type in the .then method should respect the invariants imposed by the this type and produce the same error as making an explicit call to .then.

Metadata

Metadata

Assignees

Labels

Fix AvailableA PR has been opened for this issueNeeds InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions