Skip to content

Promise<non-void> not assignable to Promise<void> w/fn return type #49755

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

Open
wbt opened this issue Jul 1, 2022 · 5 comments
Open

Promise<non-void> not assignable to Promise<void> w/fn return type #49755

wbt opened this issue Jul 1, 2022 · 5 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@wbt
Copy link

wbt commented Jul 1, 2022

Bug Report

🔎 Search Terms

promise void assignable 2345

🕗 Version & Regression Information

⏯ Playground Link

Playground link with relevant code

💻 Code

//Set 1: Works fine, as expected
const takesVoidReturningFunction = function(starter: (aStr: string) => void) {/*...*/}
const voidReturningFunction = function(aStr: string) : void {return;}
const boolReturningFunction = function(aStr: string) : boolean {return true;}
takesVoidReturningFunction(voidReturningFunction);
takesVoidReturningFunction(boolReturningFunction);

//Set 2: The async version of set 1
const takesVoidReturningAsyncFunction = function(starter: (aStr: string) => Promise<void>) {/*...*/}
const voidReturningAsyncFunction = async function(aStr: string) : Promise<void> {return;}
const boolReturningAsyncFunction = async function(aStr: string) : Promise<boolean> {return true;}
takesVoidReturningAsyncFunction(voidReturningAsyncFunction);
takesVoidReturningAsyncFunction(boolReturningAsyncFunction); //Error here:
//Argument of type '() => Promise<boolean>' is not assignable to parameter of type '() => Promise<void>'.
//  Type 'Promise<boolean>' is not assignable to type 'Promise<void>'.
//    Type 'boolean' is not assignable to type 'void'.ts(2345)

//Set 3: A simpler version of set 2
const takesVoidResolvingPromise = function(starter: Promise<void>) {
	starter.then(function() {/*...*/});
}
const voidResolvingPromise = Promise.resolve();
const boolResolvingPromise = Promise.resolve(true);
takesVoidResolvingPromise(voidResolvingPromise);
takesVoidResolvingPromise(boolResolvingPromise); //Error here:
//Argument of type 'Promise<boolean>' is not assignable to parameter of type 'Promise<void>'.
//  Type 'boolean' is not assignable to type 'void'.ts(2345)

🙁 Actual behavior

Errors as noted in code comments.

🙂 Expected behavior

Async functions that resolve non-void are assignable to async function types that resolve void, just like synchronous functions that return non-void are assignable to synchronous function types that return void.

The string parameter is given in the example code to maintain the idea that the function may have desirable side effects and thus be worth calling, even if the resolve value isn't relevant to the calling context.

The FAQ states (with only example function names changed) that

functions returning non-void assignable to function returning void...is the expected and desired behavior. First, refer to the "substitutability" primer -- the fact that boolReturningFunction returns "more" information than voidReturningFunction is a valid substitution...Another way to think of this is that a void-returning callback type says "I'm not going to look at your return value, if one exists".

Why isn't the same true of async functions?
It's pretty annoying to have to write a wrapper function around every async function just to have a void-resolving function that the TypeScript compiler demands, as another example of valid JavaScript that has to be rewritten with more than just type annotations in a conversion.

@jcalz
Copy link
Contributor

jcalz commented Jul 1, 2022

Duplicate of #12871 ?

@wbt
Copy link
Author

wbt commented Jul 1, 2022

Possibly, the way some of the comments on that have leaned. Solving that one would address Set 3 above, and therefore also address this one.

However, it seems like it could also be possible to address set 2 without addressing set 3 by creating an assignability rule that only works when it's a function return type, e.g. (*) => Promise<any> being assignable to (*) => Promise<void> without Promise<any> being assignable to Promise<void> in order to avoid potential issues with Promise.all() that seem to be holding up a solution to the other issue. Thus, it might be possible to fix and close this issue while the other remains open.

@GauBen
Copy link

GauBen commented Jul 25, 2023

Current workaround: use Promise<unknown> instead of Promise<void>

@ackvf
Copy link

ackvf commented Sep 16, 2023

This caught me off guard.

  const options: {
    action1: () => void
    action2: () => Promise<void>
    action3: () => Promise<void> | void
    action4: () => Promise<void> | void
  } = {
    action1: () => true,                  // OK (expected)
    action2: () => Promise.resolve(true), // Type 'Promise<boolean>' is not assignable to type 'Promise<void>'.
    action3: () => true,                  // Type 'boolean' is not assignable to type 'void | Promise<void>'.
    action4: () => Promise.resolve(true), // Type 'Promise<boolean>' is not assignable to type 'void | Promise<void>'.
  }

@Mahi
Copy link

Mahi commented Sep 25, 2023

Tried implementing a simple write stream:

interface OutputStream<T> {
  /** resolve when data is written successfully, reject if failed to write */
  write(data: T): Promise<void>;
}

Now I want my HTTP client to implement this:

class HttpClient implements OutputStream<HttpRequest> {
  write(data: HttpRequest): Promise<HttpResponse>;
}

Oops, not possible. Even though it should work fine when used:

const http = new HttpClient();
const res = await http.write(...); // HttpResponse

const ws: OutputStream<HttpRequest> = http; // hide object behind interface
await ws.write(...); // void return type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

7 participants