-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Promise<Promise<T>> cannot exist in JS #27711
Comments
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 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, Why does Also why can't we assign a |
@hcomnetworkers 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<T> === Promise<Promise<T>> === Promise<Promise<Promise<T>>> //... |
The 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 |
@DanielRosenwasser @weswigham @rbuckton would this suffice in 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>
} |
#35998 (comment) will make
|
We are pushing |
The solution would be for 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> |
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 What's the current state of this issue? |
#40006 mentions it, and #40002 mentions it. @ahejlsberg @RyanCavanaugh we should determine whether the |
Suspicious: return axios.request(deleteRequest); Especially being called towards the _end of the program_ this might result in the HTTP response not being waited for at all. If it is waited for/received then, depending on other weaknesses in the program, it's unclear which part of the program is supposed to catch and handle an unexpected HTTP response. Root problem: forgetting to put the `await` keyword where it has to be put. Carefully putting the `await` keyword where it has to be put is on the developer -- tooling rarely helps. See eslint/eslint#13567 When missing an `await` keyword then the value propagating through code is often not what we think it is. With a weak return type signature such as `Promise<any>` the _actual_ return value might in fact be a Promise to a Promise. This is dangerous territory; I was peeking into microsoft/TypeScript#27711 two things are clear from this issue: - TypeScript is in flux w.r.t. situations involving things like Promise<Promise<T>> - I don't even want to know about this topic The Delete() method is called here: https://github.com/opstrace/opstrace/blob/f610692c86d0da1d16165c89c22d814594940523/lib/dns/src/index.ts#L208 via redux-saga. Redux-saga has its own fancy interaction with async functions, and its own error handling paradigms. I assume that as of the missing `await` and the weak return type signature the Delete() method might in fact have been subject to a race condition, leaving a "dangling thing" behind with no proper error handling mechanism looking at that thing anymore after submission. That is, it's likely that sometimes the program either didn't wait for receiving the HTTP response or didn't have an error handler taking action when a bad response was received. Signed-off-by: Dr. Jan-Philip Gehrcke <jp@opstrace.com>
Suspicious: return axios.request(deleteRequest); Especially being called towards the _end of the program_ this might result in the HTTP response not being waited for at all. If it is waited for/received then, depending on other weaknesses in the program, it's unclear which part of the program is supposed to catch and handle an unexpected HTTP response. Root problem: forgetting to put the `await` keyword where it has to be put. Carefully putting the `await` keyword where it has to be put is on the developer -- tooling rarely helps. See eslint/eslint#13567 When missing an `await` keyword then the value propagating through code is often not what we think it is. With a weak return type signature such as `Promise<any>` the _actual_ return value might in fact be a Promise to a Promise. This is dangerous territory; I was peeking into microsoft/TypeScript#27711 two things are clear from this issue: - TypeScript is in flux w.r.t. situations involving things like Promise<Promise<T>> - I don't even want to know about this topic The Delete() method is called here: https://github.com/opstrace/opstrace/blob/f610692c86d0da1d16165c89c22d814594940523/lib/dns/src/index.ts#L208 via redux-saga. Redux-saga has its own fancy interaction with async functions, and its own error handling paradigms. I assume that as of the missing `await` and the weak return type signature the Delete() method might in fact have been subject to a race condition, leaving a "dangling thing" behind with no proper error handling mechanism looking at that thing anymore after submission. That is, it's likely that sometimes the program either didn't wait for receiving the HTTP response or didn't have an error handler taking action when a bad response was received. Signed-off-by: Dr. Jan-Philip Gehrcke <jp@opstrace.com>
So what is the status of this? It is a real deal-braker for function composition. You can't do any composition of functions that returns promises because you end with |
Is there any generic workaround for this, even an ugly one? |
TypeScript Version: Version 3.2.0-dev.20181011
Search Terms:
is:open label:Bug promise
label:Bug wrapped promise
label:Bug wrapped nested
Code
Expected behavior:
Compilation should fail, because
p1
is actually aPromise<number>
due to promise unwrapping.Actual behavior:
Compilation should fail, requiring code which looks like:
Playground Link:
Runtime error
No runtime error
Related Issues:
Didn't find related issue
The text was updated successfully, but these errors were encountered: