Home > front end >  Unioning Promise in Typescript
Unioning Promise in Typescript

Time:07-17

  • To summary the question, I'm asking that is unioning type inside generic and outside generic makes different type.

I'm making API server with express and typescript, and I made wrapper function because return type is formed.

The wrapper function calles this Handler.

export type Handler = (req: Request, 
                       res?: express.Response, 
                       next?: express.NextFunction) => (Response<any> | Promise<boolean>)

If handler returns object, wrapper response with this value. And if handler returns boolean, it does another action like calling next router.

Response is formed like this:

type Response<T> = Promise<ResponseSuccess<T> | ResponseError<T>>;

export type ResponseSuccess<T> = {
    data: T
}

export type ResponseError<T> = {
    error: string
    code: number
    data?: T
}

And I made auth handler like this:

export const auth = (permission?: Object): Handler => {
    return async (req, res) => {
        if (!req.auth) return {error: 'Authorization required', code: 401};
        if (typeof permission === 'object') {
            for (let key in permission) {
                if (req.auth.permission[key] !== permission[key]) return {error: 'Permission denied', code: 403};
            }
        }
        return false;
    }
}

I thinked that Response<any> | Promise<boolean> becomes Promise<ResponseSuccess<T> | ResponseError<T>> | Promise<boolean>, and it becomes Promise<ResponseSuccess<T> | ResponseError<T> | boolean>. And auth returns ResponseError or false, so I thinked it makes sense.

But the error occurs when I tries to build it:

error TS2322: Type '(req: Request, res: Response<any, Record<string, any>>) => Promise<false | { error: string; code: number; }>' is not assignable to type 'Handler'.
  Type 'Promise<false | { error: string; code: number; }>' is not assignable to type 'Promise<boolean> | Resp<any>'.
    Type 'Promise<false | { error: string; code: number; }>' is not assignable to type 'Promise<boolean>'.
      Type 'false | { error: string; code: number; }' is not assignable to type 'boolean'.
        Type '{ error: string; code: number; }' is not assignable to type 'boolean'.

 41     return async (req, res) => {
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 42         if (!req.auth) return {error: 'Authorization required', code: 401};
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... 
 48         return false;
    ~~~~~~~~~~~~~~~~~~~~~
 49     }

The error disappears when I remove return false;.

What is the problem? Is unioning inside generic and outside generic makes different result? Or there is another error I didn't found?

CodePudding user response:

It would be a better idea to define Response<T> to be ResponseSuccess<T> | ResponseError<T> rather than making it a Promise. It makes the three Response types consistent with one another and makes it clearer when something is really a Promise. Then Handler's return type can be Promise<Response<any> | boolean>.

is unioning type inside generic and outside generic makes different type.

Yes. For example, this expression...

const _: Promise<string> | Promise<number> = new Promise(() => Math.random() === 0 ? "A" : 1);

...causes this error.

Type 'Promise<string | number>' is not assignable to type 'Promise<string> | Promise<number>'.
  Type 'Promise<string | number>' is not assignable to type 'Promise<string>'.
    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.

In this particular case I believe they are functionally equivalent but that’s not true in general. For example [1, “A”] can be typed as (string | number)[] but it is neither a string[] nor a number[].

The error disappears when I remove return false;.

return false is outside of the anonymous async function, it's in auth itself.

  • Related