Skip to content
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

Cannot await Thenable #55408

Open
rotu opened this issue Aug 17, 2023 · 9 comments
Open

Cannot await Thenable #55408

rotu opened this issue Aug 17, 2023 · 9 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@rotu
Copy link

rotu commented Aug 17, 2023

🔎 Search Terms

"Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.", "TS1320"

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about async

⏯ Playground Link

https://www.typescriptlang.org/play?target=9#code/PTAEAEBcEMCcHMCmkBcoCiBlATABm9gFCEDGA9gHYDOkoAtgJ4AqAFohdAEYA2ioAvKADehUKEhsKACkoAxAK7cAZgEtuvACYAaUJQBKiAFaISkRBoCUQ0LGTzYFXRQXK1mgPwA6KQEYLoAF9CIMJoKgYKElAleUjIFUpQaDpoFWl-ETFQcmpaWAEkgHdU2kZWdi5eAG5RMRyqMl5PbjJ4KQByADdobnlEdp1YHXaUAfEGAAdEMiUbCxqs20h7R1gakLLJSr4qaHiqVUQqUAAFWDI6FSpEABkVAGtEAB4KeTpORFgAPmJryCYVHRpvJIFJkqkKDpcBYgA

💻 Code

// @target: ES2022

const myThenable = {
  then(onFulfilled, onRejected){ return onFulfilled?.(1) }
}

async function amain() {
   const r = await myThenable;
   console.log('value', r, ':', typeof r);
   return r;
}

myThenable satisfies PromiseLike<number>

setTimeout(amain, 0)

Workbench Repro

🙁 Actual behavior

An error is shown:

Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.

🙂 Expected behavior

I expect no error or a suppressible warning. I also expect some explanation of "why" or a suggestion like "wrap the thenable in a Promise.resolve()"

Additional information about the issue

This is valid JavaScript, and executes in Deno, Node, Chrome, and Firefox.

Note that await doesn't even require its argument to be a PromiseLike - then may have return type void or even never!

@MartinJohns
Copy link
Contributor

MartinJohns commented Aug 17, 2023

Your onFulfilled must be typed as a function, then it works.

I expect no error or a suppressible warning

It is suppressible using // @ts-ignore. Also, TypeScript does not have a concept of a "warning", they're all errors.

@rotu
Copy link
Author

rotu commented Aug 17, 2023

Your onFulfilled must be typed as a function, then it works.

onFulfilled in the example has type any, which contains Function. What type annotation did you have in mind?

I expect no error or a suppressible warning

It is suppressible using // @ts-ignore. Also, TypeScript does not have a concept of a "warning", they're all errors.

It’s also suppressible with // @ts-nocheck :-).

Some things like noUnusedLocals and noImplicitAny are suppressible in the config. This is not.

@MartinJohns
Copy link
Contributor

MartinJohns commented Aug 17, 2023

What type annotation did you have in mind?

() => any

Some things like noUnusedLocals and noImplicitAny are suppressible in the config. This is not.

Those are about stylistic choices, not actual type errors.

But I agree that the error message could be better.

@rotu
Copy link
Author

rotu commented Aug 17, 2023

What type annotation did you have in mind?

() => any

It's not correct to type onFulfilled:()=>any since:

  1. That type is not callable with value 1.
  2. onFulfilled in reality has return type undefined (I'm not sure if this is always the case) as you can see by running this in a repl:
(async ()=>{
  const result = await ({then:(f,r)=>{console.log('onFulfilled returned:',f(1))}})
  console.log('promise resolved to:',result)
})()

For me, the above prints:

onFulfilled returned: undefined
promise resolved to: 1

EDIT: maybe you meant onFulfilled:((v:unknown)=>unknown) which DOES seem to make TS1320 go away. That's super uninintuitive because, contrary to the error message, it doesn't my myThenable a "valid promise".

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Aug 17, 2023
@jcready
Copy link

jcready commented Aug 18, 2023

Not sure if related, but this also fails for reasons I don't understand:

interface MaybeThenable {
  then?: PromiseLike<any>["then"];
}

async (myThenable: MaybeThenable) => {
  if (myThenable.then) {
    await myThenable;
  }
}

The error message:

Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.

@Andarist
Copy link
Contributor

It's not a discriminated union and this property refinement doesn't get "transferred" on the object itself once you pass that object into a function (and await operator :P). This is a known limitation. One of the related issues can be found here

@jcready
Copy link

jcready commented Aug 18, 2023

You don't need to refine actually:

interface MaybeThenable {
  then?: PromiseLike<any>["then"];
}

async (myThenable: MaybeThenable) => {
  await myThenable;
}

Either: A) myThenable doesn't have a callable 'then' member and thus it can be safely awaited or B) it is conforming to PromiseLike<any> and can be safely awaited.

@Andarist
Copy link
Contributor

Andarist commented Aug 18, 2023

Ah, I see. getAwaitedTypeNoAlias + getAwaitedTypeNoAlias lack some handling of unions

@RealAlphabet
Copy link

Is there any progress?

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