0

I was trying to type annoate a version of the reflect promise method from here - https://stackoverflow.com/a/31424853/1828637

function reflectPromise(p){
    return p.then(data => ({
                data,
                resolved: true
             }))
            .catch(error => ({
                error,
                rejected: true
             }));
}

What it does is takes a promise, and returns another promise when it is resolved or rejected.

The things I'm trying to do with pseudocode:

  1. Declare that data is typeof ResolveValue(p)
  2. Declare that error is typeof RejectValue(p)
  3. Declare that others can test const didReject = !!(await (reflectedPromise(somePromise)).rejected (what this will do for resolved promises, which returns { data: xxx, resolved:true }) is turn undefined to true. Currently when I do !!blah.rejected TypeScript says to me Property 'rejected' does not exist on type

This is what I have so far:

function reflectPromise(p: Promise<any>): Promise<
        { data: any, resolved: boolean, rejected: void  } |
        { error: any, resolved: void, rejected: boolean }
    > {
    return p.then(data: any) => ({
                data,
                resolved: true
             }))
            .catch((error: any) => ({
                error,
                rejected: true
             }));
}
Noitidart
  • 35,443
  • 37
  • 154
  • 323

1 Answers1

2

You need to use a generic type to have the type of the result inferred. The type of the error is considered any in Typescript and there is no type safety there. Also I would type rejected and resolved as undefined not void (their value will be undefined after all at runtime so it's more acurate) and I would make them optional when they are not present.

Also when resolve and reject are true, I would type them as the boolean literal type true in order to allow type-guards to work better.

Putting it together, this compiles (with strict null checks):

function reflectPromise<T>(p: Promise<T>): Promise<
        { data: T, resolved: boolean, rejected?: undefined  } |
        { error: any, resolved?: undefined, rejected: boolean }
    > {
    return p.then((data: any) => ({
                data,
                resolved: true
            }))
            .catch((error: any) => ({
                error,
                rejected: true
            }));
}


(async function (somePromise: Promise<number>) {
    const result = await (reflectPromise(somePromise));
    const didReject = !!result.rejected
    if (result.rejected) {
        result.error // result is { error: any, resolved?: undefined, rejected: true }
    } else {
        result.data // result { data: number, resolved: true, rejected?: undefined  } 
    }

    if (result.resolved) {
        result.data // result { data: number, resolved: true, rejected?: undefined  } 
    } else {
        result.error // result is { error: any, resolved?: undefined, rejected: true }
    }
})(Promise.resolve(1));

Also the implementation of reflectPromise looks better with async/await in my opinion:

async function reflectPromise<T>(p: Promise<T>): Promise<
    { data: T, resolved: true, rejected?: undefined } |
    { error: any, resolved?: undefined, rejected: true }
> {
    try {
        return {
            data: await p,
            resolved: true
        }
    } catch (e) {
        return {
            error: e,
            rejected: true
        }
    }
}

Without strict null checks, type guard will partially work if we need to change the types a bit, and set both resolved and reject on both branches:

async function reflectPromise<T>(p: Promise<T>): Promise<
    { data: T, resolved: true, rejected: false } |
    { error: any, resolved: false, rejected: true }
> {
    try {
        return {
            data: await p,
            resolved: true,
            rejected: false,
        }
    } catch (e) {
        return {
            error: e,
            rejected: true,
            resolved: false
        }
    }
}

(async function (somePromise: Promise<number>) {
    const result = await (reflectPromise(somePromise));
    const didReject = !!result.rejected
    if (result.rejected) {
        result.error // result is { error: any, resolved?: undefined, rejected: true }
    } 

    if (result.resolved) {
        result.data // result { data: number, resolved: true, rejected?: undefined  } 
    }
})(Promise.resolve(1));
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 1
    Super super thank you for the explanations here! I'm new to TypeScript so learning a lot. This is super useful I wish I could give you more up vote points!! Especially notes on `void`, and strict null checking, it pointed me to looking into learning those differences now. – Noitidart Sep 19 '18 at 22:52