microsoft / microsoft/TypeScript

`.then(onresolved)`'s return type should not be inferred from contextual type

Open
#54,551 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Domain: lib.d.ts Possible Improvement
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

lib Update Request

Configuration Check

My compilation target is ES2015 and my lib is the default.

Also reproducible with target ESNext, ES2022 ect.

Missing / Incorrect Definition

Promise.prototype.then

Sample Code

TypeScript infers the return type of then from the contextual type information when called with only one argument.
This can cause unexpected results

let a: string | undefined
const b = a ?? await somePromise.then(() => "str")

I expect b to be string because either a is a string, somePromise resolves and then returns a string, or somePromise rejects and await throws so b is not assigned.
However b is actually string | undefined because the contextual type on the RHS of a nullish operator is the type of the LHS
and then inferred it's return type from the context because TResult2 was not inferred from arguments.

b satisfies string | undefined

I think there's actually two issues here

  • The contextual type of the RHS of nullish operator is the type of the LHS
  • .then's type definition does not use overloads for different number of arguments

The first issue seems debatable, I can understand the current behavior but these alternates make sense as well

  1. The contextual type of R should be unknown or the same as the whole L ?? R expression
  2. The contextual type of R should be NonNullable<L>

Let me know if this should be a separate issue or if there is interesting reading on the current behavior.

The next issue is caused because the return type of .then is Promise<TResult1 | TResult2> and TResult2 is not inferred from then's arguments when onrejected is not provided so it's inferred from the contextual type.

I think this issue can be fixed by changing the library to use overrides

then<TResult1>(onresolved: (t: T) => TResult1): Promise<TResult1>
then<TResult1, TResult2>(onresolved: (t: T) => TResult1, onrejected: (err: any) => TResult2): Promise<TResult1 | TResult2>
// more overrides for 0 or only onrejected args.

Documentation Link

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
https://github.com/microsoft/TypeScript/blob/main/src/lib/es5.d.ts#L1524

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Promise.prototype.then definition in src/lib/es5.d.ts around line 1524 and reproduce the supplied playground example. Check how the one-argument and two-argument forms are typed, and determine whether the nullish-operator contextual typing concern should be handled separately. Done means the intended type of the one-argument form is verified without regressing the documented Promise behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
compilers
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.