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

Proposal: ThenArg #38

Closed
5c077yP opened this issue May 23, 2019 · 15 comments
Closed

Proposal: ThenArg #38

5c077yP opened this issue May 23, 2019 · 15 comments

Comments

@5c077yP
Copy link

5c077yP commented May 23, 2019

Hey there,

would be nice to include the helper type to unwrap a PromiseLike:

e.g.

type ThenArg<T> = T extends Promise<infer U> ? U : T

my main motivation is when using jsdoc in js I often declare a let without any initialisation and assign a value in a later closure (mostly when running mocha tests), e.g.:

describe('some block', () => {
  /** @type {ThenArg<ReturnType<loadData>>} */
  let data; // <-- data is untyped if not defining the @type above
  before('load data', async () => {
    // loadData is typed here and returns a valid type, but data keeps being set to any
    data = await loadData();
  })

  it('has loaded to correct data', () => {
    // ...
  });
})
@sindresorhus
Copy link
Owner

Can't say I've ever personally needed this, but I'll leave it open for feedback and votes. If we were to accept it, I think it should be called UnwrapPromise.

@fabiospampinato @kainiedziela @WORMSS Thoughts?

@sindresorhus
Copy link
Owner

Just noticed #42, which is basically this.

@kainiedziela
Copy link
Contributor

A promise is usually typed, so wrapping that in another type seems redudant to me. In the above example using the loadData type would be sufficent, correct?

@WORMSS
Copy link

WORMSS commented Jul 2, 2019

I would think it would be most likely be useful in a declaration file but I've had a long day and struggling to think of a real world example currently.

@fabiospampinato
Copy link
Contributor

fabiospampinato commented Jul 2, 2019

There might be some use cases for this, like crafting some other complicated type maybe, but the use case provided by @5c077yP is a bit strange IMHO:

/** @type {ThenArg<ReturnType<loadData>>} */
let data; // <-- data is untyped if not defining the @type above
data = await loadData();

Here the return type of loadData never changes, so you might as well just write that in the TSDoc comment (ie. if it's Promise<object> just write object, if it's Promise<MyData> just write MyData).

If instead the function to await is being passed dynamically we would still have to constrain the type of the allowed function, otherwise we would just get any out, and I think just writing the expected return type instead of ThenArg<ReturnType<TypeOfAllowedFunctions>> will probably be cleaner 🤔

Definitely UnwrapPromise is a much better name.

Maybe this type should check if the passed type is a PromiseLike rather than a Promise, just to have a more general implementation.

@streamich
Copy link

streamich commented Jul 2, 2019

An often use case is to use in in conjunction with ReturnType when inferring return type of an async function. For example, it could be a library that exports a type of an async function, and you want to get the return type of it.

UnwrapPromise<ReturnType<Fn>>

like this

image

or this

image

@fregante
Copy link

fregante commented Jul 7, 2019

This doesn't seem specific to Promises. It can work for any generic like:

GenericArg<Partial<Options>> === Options
GenericArg<Array<Function>> === Function
GenericArg<NodeListOf<TextNode>> === TextNode

Or

Unwrap<Promise<Data>> === Data

Edit: this doesn't seem to be possible at all now; an Unwrap type has to extend something.

@5c077yP
Copy link
Author

5c077yP commented Jul 8, 2019

@sindresorhus sorry for the maybe not awesome example i gave.

I fully agree with @streamich , infering the type of an async function is an often use case, where the result of this function is not publicly exported/available to use directly.

The case I tried to show is basically the same. The result of the async function loadData is not available but the variable declaration is split from the initialisation , which is why the typescript compiler is not able (at least for me) to infer the type correctly.

Any further comments on this? Could I somehow help getting this forward?

@kainiedziela
Copy link
Contributor

Okay, I can see this being useful, but doesn't it promote bad habits? Why would the typeof a promise's result not be available to use directly? Shouldn't you type it and then the UnwrapPromise type would be obsolete?

What's the advantage of using UnwrapPromise<PromiseLike<T>> over T?

@5c077yP
Copy link
Author

5c077yP commented Jul 8, 2019

@kainiedziela might be true that this isn't a good habit.

I'm using jsdoc in js extensively , so we're not using typescript directly and there not all type information is always available, e.g. you have to export things which should be available, but that's not always the case.

I guess this also applies to transitional phases, where types are just getting added step by step.

@streamich
Copy link

@kainiedziela

What's the advantage of using UnwrapPromise<PromiseLike<T>> over T?

T might not be available. It is basically the same as using ReturnType<() => T> over T.

@Schniz
Copy link

Schniz commented Sep 17, 2019

I have implemented it in a couple of projects. Some times it was recursive (UnwrapPromise<Promise<Promise<T>>>) to work like await

@resynth1943
Copy link
Contributor

type UnwrapPromiseRecursive<TPromise> = TPromise extends Promise<infer TValue> ? UnwrapPromiseRecursive<TValue> : TPromise;

That can do recursive promise unwrapping.

@resynth1943
Copy link
Contributor

This has been added as PromiseValue.
/cc @sindresorhus @5c077yP

@sindresorhus
Copy link
Owner

Fixed by #75.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants