Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jmartinezmaes opened this issue Feb 3, 2022 · 0 comments Β· Fixed by #48946
Closed

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

jmartinezmaes opened this issue Feb 3, 2022 · 0 comments Β· Fixed by #48946
Assignees
Labels
Fix Available A PR has been opened for this issue Needs Investigation This issue needs a team member to investigate its status.

Comments

@jmartinezmaes
Copy link

jmartinezmaes commented Feb 3, 2022

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.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Feb 7, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.7.0 milestone Feb 7, 2022
@typescript-bot typescript-bot added the Fix Available A PR has been opened for this issue label May 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fix Available A PR has been opened for this issue Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants