Typescript: 3.7-beta regression: Promise.all wrongly adds `undefined` type

Created on 2 Oct 2019  路  25Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.7.0-beta


Search Terms:

promise all

Code

interface A {
  a: string
}
interface B {
  b: string
}
async function main(): Promise<A> {
  const [a, b] = await Promise.all([
    Promise.resolve({a: "a"} as A),
    Promise.resolve({b: "b"} as B | undefined), // <- the `| undefined` here is the problem
  ]);
  return a; // <- a has type `A | undefined`, but should be `A`
}

Expected behavior:

This is a regression from 3.6.3, where the return tuple of Promise.all is correctly inferred.

Actual behavior:

A | undefined bound of a different tuple member also adds a | undefined bound on a different unrelated return value of Promise.all.

Playground Link:

https://www.typescriptlang.org/play/?ts=3.7-Beta#code/JYOwLgpgTgZghgYwgAgILIN4ChnLgLmQGcwpQBzLAXy1ElkRQCFMdkAjQkskSmuIgE8QCZDACuIsMAD2IZAFs4oABQBKQgAUoMhcCIQAPKgB8rXAjklkAbTgAaDgF1kAXjwB3ZWGTbd+iAA6OAAbEJUbNlw-PQNAqAgiGRCANwgVDAJkACI4bKo8IjQ1eyjfHVighKTU9IxOHPZ8wuQWAB9kSQATCBhQCC6S5AB6YeRDAFpkMAALFAADDu7e-q755DmE5H1pueQABx12EIgFNic1AG42BLBxKHk4S5GxybwNgWnBfYX0JZAen0QAN5o52OIfEQZjJxCEuhxfvNqEA

Related Issues:

Maybe https://github.com/microsoft/TypeScript/pull/33707 ?

Bug lib.d.ts

Most helpful comment

@RyanCavanaugh What's the timeline for getting this fixed?
It's been around for a while now, and it's a pretty major regression, blocking us from upgrading...

All 25 comments

I think it's caused by #33057 which is merged; #33707 is not merged so will not be in the beta.

Hm, but maybe #33707 will resolve this regression? That鈥檚 what I thought, haven鈥檛 checked though.

Reduced repo:

interface A {
  a: string
}
interface B {
  b: string
}

interface Foo {
  all<T1, T2>(values:  readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
}

declare const Foo: Foo;

function main() {
  let aValue: A;
  Foo.all([Promise.resolve({a: "a"} as A), Promise.resolve({b: "b"} as B | undefined)]).then(([a,b]) => aValue = a);
}

Error on assignment to aValue; removing readonly removes the error.

Yes, the typings in #33707 fix the smaller repro above.

sed -i 's|: readonly|:|g' node_modules/typescript/lib/lib.es2015.promise.d.ts seems to be an effective temporary workaround until this is fixed.

@buu700 how do you recommend using that work around? running that command prior to compile every time?

@RyanCavanaugh this seems like a pretty major regression, is there any timeline here? I see this immediately on upgrading all around my code base.

You could use the attached patch with patch-package, or possibly run that sed command (or a JS equivalent) in a postinstall script.

typescript+3.7.2.patch

@buu700 this is amazing. thank you so much for the suggestion extremely helpful!

I'm seeing a similar situation in Promise.all in 3.7.2 but types are being overwritten.

Ex. in the example below r1 should be of type boolean and r2 should be of type unknown but actually they are both of type unknown.

        const p1: Promise<boolean> = new Promise((resolve) => { resolve(true); });
        const p2: Promise<unknown> = new Promise((resolve) => { resolve(); });
        const [r1, r2] = await Promise.all([p1, p2]);

If p2:Primose<undefined> then r1 and r2 are of type boolean | undefined

Playground: https://www.typescriptlang.org/play/index.html#code/IYZwngdgxgBAZgV2gFwJYHsIwLbFRACgEoYBvAKBhikxGRgAcBGALhgAUAndbVEAUwA8AI3ToANv2AQAfDAC8MCPwDuHbrwEECnfiAkA3fiXlzSMXfvFGCyTgmMBuGAF8ijytVr0GAJjZcPHxCSADWEOgqsgpKqupBWjp6hsYKZhbJ1vzEzm4eVDQQdDAA2pxMADQWvgC6McAqePSBmvwAdMDi4gQlzFV+Ne7kLuTkoJCwiCgYWLj4vsRknoXFzAEawSJiktJyispqLcHalikm6adZtvZOrkMF3oz+8a2CSAAm-HD4-O97sYcNolLkZzmQMlYbO47vkvEV6GVKtU6ooGk0XsEOl0en0noMPCMxuBoPAkFA0JgcHgIABmRYUB7wxisDECLYSKTRfZxI7AzKgtLgkHZOwOaF5ZaPPzrBJCCAILr-A6s7JJSGpUxC-nZcX3OHFRFVTi1eqNVDNIHtTrdXpIgZDFxAA

Is this not a real issue? I'm getting it, and it seems to me that it is generally pretty disruptive, but I'm not seeing enough MS chatter about it to confirm that it's not just a legit new type error that I don't understand.

@JasonKleban This is a legit regression. Just updated a codebase and am seeing this.

Easily reproducible:

const [num1, num2] = await Promise.all([Promise.resolve(1), Promise.resolve(null)]);
Causes the type for num1 to be number | null.

still reproducible in 3.7.3 and nightly

We are having this issue in 3.7.3 as well. It is blocking our upgrade off of 3.6.x (hundreds of occurrences across the code base).

@RyanCavanaugh What's the timeline for getting this fixed?
It's been around for a while now, and it's a pretty major regression, blocking us from upgrading...

sed -i 's|: readonly|:|g' node_modules/typescript/lib/lib.es2015.promise.d.ts seems to be an effective temporary workaround until this is fixed.

Is there any reason my fix from November (quoted above) can't or shouldn't be used?

I don't mind continuing to patch TypeScript in my own projects as a temporary workaround, but it just seems odd that such a high-impact regression with a simple quick fix available is still an issue three months later.

tbh, I鈥檓 a bit disappointed by the lack of communication here. I recently read the already month old Design notes here: https://github.com/microsoft/TypeScript/issues/36138 which mention ideas to create a dedicated awaited or promised type, which would maybe solve this, but is probably a very invasive change.
However, this is just speculation on my part. Some official clarification would be nice.

v3.7.5 02.20 and I still facing the same issue on PromiseLike types via lib.es5.d.ts

Assuming we merge the awaited type PR, I plan to take a renewed look at all of the Promise-related issues in light of that change.

This issue can be worked around by adding this somewhere:

declare global {
    interface PromiseConstructor {
        all<T extends readonly any[] | readonly [any]>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;
    }
}

but I'd be really happy having this fixed, my devs are using this as an excuse to plaster Bluebird everywhere instead of using native promises.

Still exists in 3.9.0-dev.20200229

We anticipate that our next version, TypeScript 3.9, will be coming mid-May of 2020, and will mostly focus on performance, polish, and potentially smarter type-checking for Promises.

Hopefully this issue will be fixed in 3.9

Seems this example still does not work on nightly.

(async () => {
    const [num1, num2] = await Promise.all([Promise.resolve(1), Promise.resolve(null)]);
    num1.toString();
})

Is it really fixed?

Just checked on 3.9.0-dev.20200317 and it appears to be fixed. woohoo!

The playground seems to have a lot of weirdness there, specifically with Promise.all. Things like signature help don't even seem to work correctly. I'm not sure exactly what the problem is, but if you do try that code out locally with a nightly version of TypeScript, it should all work fine.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

siddjain picture siddjain  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments