trusktr
trusktr

Reputation: 45484

Map conditional types back to a union type?

I am trying to do the following (also see it on TypeScript playground), but I get an error on the return type of the function that tells me the conditional type can not be assigned to the union:

type RequestType =
  | 'foo'
  | 'bar'
  | 'baz'

interface SomeRequest {
  id: string
  type: RequestType
  sessionId: string
  bucket: string
  params: Array<any>
}

type ResponseResult = string | number | boolean

async function sendWorkRequest<T extends RequestType>(
    type: T,
    ...params
  ): Promise<
    T extends 'foo'
      ? string
      : T extends 'bar'
        ? number
        : T extends 'baz' ? boolean : never
  > {
    await this.readyDeferred.promise

    const request: SomeRequest = {
      id: 'abc',
      bucket: 'bucket',
      type,
      sessionId: 'some session id',
      params: [1,'two',3],
    }
    const p = new Promise<ResponseResult>((/*...*/) => {/*...*/})

    this.requests[request.id] = p
    this.worker.postMessage(request)
    return p // <-------------------------------- ERROR
  }

Basically, I want the conditional type to result in one of the types of the ResponseResult type. So based on the type argument passed into the function, it should return one of the types in the ResponseResult union (as a Promise).

How can I make this work, so that the type of the type argument determines the type of Promise returned?


Here's another way to do it without conditional types, but I want to know if it can be done using the conditional type for the type arg.


EDIT: Based on Erik's answer below, I am also curious why this one won't work, and if it is possible to make it work without redefining ResponseResult and without changing the return type of the function.

@Erik, second example.

Upvotes: 3

Views: 273

Answers (2)

Erik Philips
Erik Philips

Reputation: 54628

You need to encapsulate the Type as (assumption) typescript can't assume (calculate) two non-referenced conditional types are the same.

So instead do

type ResponseResult<T> =
  T extends 'foo'
    ? string
    : T extends 'bar'
      ? number
      : T extends 'baz' ? boolean : never;

Now you can change the signature of the function to:

async function sendWorkRequest<T extends RequestType>(
  type: T,
  ...params
  ): Promise<ResponseResult<T>> {

and update p:

const p = new Promise<ResponseResult<T>>(() => { });

TypeScript Playground Example

Do you know why or how to do it without changing the return type and without modifying the definition of the return type?

No because a Conditional Type not equal to a Type.

Does that one require an explicit type cast in the place where the function is called?

No, I can mock the promise with a property of type and see the it is of that type:

TypeScript Playground Example

Is there a way to make it safe (no type cast)?

Not necessary

Upvotes: 2

Patrick Roberts
Patrick Roberts

Reputation: 51886

Alternatively in order to get type inference working you can provide override declarations:

type ResponseResult = string | number | boolean

async function sendWorkRequest(type: 'foo', ...params): Promise<string>
async function sendWorkRequest(type: 'bar', ...params): Promise<number>
async function sendWorkRequest(type: 'baz', ...params): Promise<boolean>
async function sendWorkRequest(type: RequestType, ...params): Promise<ResponseResult> {
  /* ... */
  const p = new Promise<ResponseResult>((/* ... */) => {/* ... */})
  /* ... */
}

// WORKS
async function test1() {
  const result = await sendWorkRequest('foo')
  result.split('')
}

test1()

Upvotes: 1

Related Questions