TypeScript Version: Version 3.2.0-dev.20181011
Search Terms:
is:open label:Bug promise
label:Bug wrapped promise
label:Bug wrapped nested
Code
const p1 = new Promise<Promise<Number>>((resolveOuter) => {
const innerPromise = new Promise<Number>((resolveInner) => {
console.log('Resolving inner promise')
resolveInner(1)
})
console.log('Resolving outer promise')
resolveOuter(innerPromise)
})
p1.then((p2: Promise<Number>) => {
p2.then((num) =>
console.log('the number is: ', num)
)
})
Expected behavior:
Compilation should fail, because p1
is actually a Promise<number>
due to promise unwrapping.
Actual behavior:
Compilation should fail, requiring code which looks like:
const p1 = new Promise<Number>((resolveOuter) => {
const innerPromise = new Promise<Number>((resolveInner) => {
console.log('Resolving inner promise')
resolveInner(1)
})
console.log('Resolving outer promise')
resolveOuter(innerPromise)
})
p1.then((p2: Number) => {
console.log('the number is: ', p2)
})
Playground Link:
Runtime error
No runtime error
Related Issues:
Didn't find related issue
@rbuckton do we have an issue tracking this?
All I have is #17077 and the design notes at #17621, #18155, #19169, #20724.
Seems like you want something like negated types (#18280 or #4196).
Seems like a legit bug, but as a side note, a little strange to resolve a promise from a promise executor callback. Promise executor should basically be reduced down to the lowest level that isn't already promisified. If you already have a promise in the executor, then you could just make that your promise call, instead of creating a wrapper promise around a promise.
We have the same problem. We wrap unknown return types into an additional Promise and can get stuck with a resolved type of Promise<Promise<T>>
if the original type was a Promise.
This nested Promise however is problematic:
async function xxx(x: Promise<Promise<string>>): Promise<void> {
x.then((y) => {
y.match(/foo/); //invalid
});
const z = await x;
z.match(/foo/); //valid
}
Here, y
is wrongfully seen as a Promise<string>
while z
is correctly seen as string
.
Why does await
and then
behave differently?
Also why can't we assign a Promise<Promise<T>>
to a Promise<T>
variable? This is valid JS since nested Promises always unwrap.
@hcomnetworkers Promise<Promise<T>>
can exist, it looks like this
const resolvesToPromise = function(){
return new Promise(r => r(new Promise(...));
}
it doesn't get unwrapped in this case since the executor resolves synchronously
@ORESoftware It does not matter how often you nest a Promise, the result is the same:
const prom = new Promise((r) => r(new Promise((r2) => r2(42))));
prom.then((x) => console.log(x)); //prints 42
console.log(await prom); //prints 42
This is just how Promises work. You simply cannot get the "inner" Promise, it's gone, flattened.
I'm not saying that the type Promise<Promise<T>>
is wrong, it might just be the result of a function, wrapping something into a Promise. However, TypeScript should always collapse nested Promises when type checking and compiling because that is what JavaScript does.
Promise<T> === Promise<Promise<T>> === Promise<Promise<Promise<T>>> //...
The Promise
type seems broken:
const a = Promise.resolve()
.then<Promise<void>, never>(() => Promise.resolve());
// b is undefined, but its type is Promise<void>
a.then(b => console.log(b));
@hcomnetworkers you're right, never would have guessed that wrt to the promise executor
I encountered this error when writing a higher-order wrapping function
async function asf(s: string) {
return s
}
const wrap = <T extends (...args: any[]) => any>(f: T) => {
return async function(this: any, ...args: Parameters<T>) {
const r: ReturnType<T> = f.apply(this, args)
// do something with r
return r
}
}
const wrappedFunc = rest(asf) // (this: any, s: string) => Promise<Promise<string>>
const ret = wrappedFunc('string') // TS gives Promise<Promise<string>> here
I have to do
return async function(this: any, ...args: Parameters<T>) {
const r: ReturnType<T> = f.apply(this, args)
return r
} as T
to get ret
typed Promise<string>
, but I'd rather not use as
@DanielRosenwasser @weswigham @rbuckton would this suffice in lib.es2015.promise.d.ts
? It's a bit unintuitive (the type of new Promise<Promise<number>>()
would actually evaluate to Promise<number>
), but I believe this provides the desired compile-time error.
Playground link with some additional testing/assertions 馃檪
export type FlattenedPromise<T> =
unknown extends T
? Promise<T>
: T extends Promise<infer U>
? T
: Promise<T>;
interface PromiseConstructor {
// <snip>
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): FlattenedPromise<T>;
// <snip>
}
https://github.com/microsoft/TypeScript/pull/35998#issuecomment-594809232 will make Promise<Promise<T>>
assignable to Promise<T>
:
This last commit adds the capability to measure a type parameter to determine whether it is
awaited
in athen
method, and if so unwraps theawaited
during assignability checks between two identical type references. What this means is that when comparing assignability between twoPromise
instantiations, the type argument of each promise is first unwrapped before assignability is compared, soPromise<awaited T>
is assignable toPromise<T>
, andPromise<Promise<Promise<T>>>
is now _also_ assignable toPromise<T>
.
We are pushing awaited
until after TS3.9 while we continue to assess the impact of the change. As a result, I am reopening this issue until we have a solution in place.
The solution would be for Promise
not to re-wrap if it's already a Promise
. That way, we can never end up with nested Promise
s. Here's a little proof of concept:
export type Promise<A extends any> =
globalThis.Promise<
A extends globalThis.Promise<infer X>
? X
: A
>
So now we can do:
type t0 = Promise<1> // Promise<1>
type t1 = Promise<Promise<1>> // Promise<1>
type t2 = Promise<Promise<Promise<1>>> // Promise<1>
awaited
keyword was in the 4.0 iteration plan (#38510), but 4.0 just released without it.
I can't find any more discussion about it. It's not in the 4.1 iteration plan (#40124), nor any Design Notes.
I found it being mentioned in #40002, but that pr didn't actually add the Awaited
type into libs right?
What's the current state of this issue?
Awaited
type alias makes sense for 4.1.
Most helpful comment
@ORESoftware It does not matter how often you nest a Promise, the result is the same:
This is just how Promises work. You simply cannot get the "inner" Promise, it's gone, flattened.
I'm not saying that the type
Promise<Promise<T>>
is wrong, it might just be the result of a function, wrapping something into a Promise. However, TypeScript should always collapse nested Promises when type checking and compiling because that is what JavaScript does.