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

More strong Promise<T> in lib.es6.d.ts #5413

Open
alisabzevari opened this issue Oct 27, 2015 · 6 comments
Open

More strong Promise<T> in lib.es6.d.ts #5413

alisabzevari opened this issue Oct 27, 2015 · 6 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@alisabzevari
Copy link
Contributor

I think it would be good to be able to define the type of error in Promise type. I suggest promise type to be something like this:

interface Promise<T,U> {
    /**
    * Attaches callbacks for the resolution and/or rejection of the Promise.
    * @param onfulfilled The callback to execute when the Promise is resolved.
    * @param onrejected The callback to execute when the Promise is rejected.
    * @returns A Promise for the completion of which ever callback is executed.
    */
    then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: U) => TResult | PromiseLike<TResult>): Promise<TResult>;
    then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: U) => void): Promise<TResult>;

    /**
     * Attaches a callback for only the rejection of the Promise.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of the callback.
     */
    catch(onrejected?: (reason: U) => T | PromiseLike<T>): Promise<T>;
    catch(onrejected?: (reason: U) => void): Promise<T>;

    [Symbol.toStringTag]: string;
}

Or at least have a Promise<T, U> type which extends Promise<T>.

@NoelAbrahams
Copy link

This is another good use-case for implementing #2175.

interface Promise<T, U = Error> {
 //  ...
}

@Arnavion
Copy link
Contributor

You didn't update the types of PromiseLike and Promise returned from then and catch. If you had, you'd see the error type becomes larger and larger, since all promises in a chain contribute to the error type (unlike the result type where only the last promise contributes to it).

Consider:

// A promise that resolves to string value or rejects with number value
var promise: Promise<string, number>;

// A promise that uses previous promise to resolve to string value or fail with Error value,
// and lets failure of previous promise fall-through.
var promise2 = promise.then(result => {
    if (result.length === 0) {
        throw new Error("");
    }
    return result;
});
  1. The type of promise2 needs to be Promise<string, number | Error>. The error type will grow as more promises are added to the chain.
  2. Without explicit type annotation, TS cannot know to append | Error to the error type since it does not know to modify the type of the callback (and by extension the generic specialization of the promise.then() call) based on throw statements in the body of the function.

So at a minimum (i.e., the best you can do with current TS syntax) you'd need to do this:

    then<TResult, TError>(onfulfilled?: (value: T) => TResult, onrejected?: (reason: U) => TResult | PromiseLike<TResult>): Promise<TResult, U | TError>;

// Explicitly need to pass in specialization of TError = Error since it is unbound otherwise.
// Even if neither callback throws a different type than U (or throws at all), this will still be needed.
var promise2 = promise.then<string, Error>(...);

To have TS bind TError implicitly, you'd need some kind of throws TError annotation on each callback's signature:

    then<TResult, TError>(onfulfilled?: (value: T) => TResult throws TError, onrejected?: (reason: U) => TResult | PromiseLike<TResult> throws TError): Promise<TResult, U | TError>;

and then require TS to be able to consider throw statements to infer the throws-type for functions and for generic specialization.

This then basically becomes checked exceptions which bring all of their associated problems (like being unable to be used with higher-ordered functions).

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Aug 18, 2016
@OliverJAsh
Copy link
Contributor

Surely we can add the error generic and it will default to any? This would mean promises can continue to be used how they are today, but with the added ability to specify the error generic in a type annotation if wanted.

For context, I am using the browser’s fetch function, and in my .catch I lose all type safety because the error is typed as any. It is easy enough to specify the parameter type in the callback, but if we improved Promise typings as suggested, this could work out of the box:

fetch('foo').catch(e => {
  e.message // string
})

Without explicit type annotation, TS cannot know to append | Error to the error type since it does not know to modify the type of the callback (and by extension the generic specialization of the promise.then() call) based on throw statements in the body of the function.

I see, however TypeScript would know to append | Error to the error type if the callback returned a promise?

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Jun 23, 2017

Workaround for fetch is to specify your own Promise interface and annotate the promise as needed:

/**
 * Represents the completion of an asynchronous operation
 */
interface PromiseWithError<T, U> extends Promise<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(
        onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
        onrejected?: ((reason: U) => TResult2 | PromiseLike<TResult2>) | undefined | null,
    ): PromiseWithError<TResult1 | TResult2, U>;

    /**
     * Attaches a callback for only the rejection of the Promise.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of the callback.
     */
    catch<TResult = never>(
        onrejected?: ((reason: U) => TResult | PromiseLike<TResult>) | undefined | null,
    ): PromiseWithError<T | TResult, U>;
}

const promise: PromiseWithError<any, Error> = fetch('foo');
const promise2 = promise.catch(e => e.message);
const promise3: Promise<any> = promise2;
promise3;

const fetchPromiseWithError = (
    url: string,
    init?: fetch.RequestInit,
): PromiseWithError<fetch.Response, Error> => fetch(url, init);

fetchPromiseWithError('foo').catch(e => e.message);

@felixfbecker
Copy link
Contributor

This should be consistent with synchronous exceptions, i.e. this should be done if and only if #13219 gets implemented

@hrajchert
Copy link

I agree the ideal solution would be to have typed exceptions and then being able to correctly type Promises... but seeing that it may take a looooong time to have typed exceptions, I created a library that works likes promises but with a subtle difference that allows me to have typed errors and a pretty good error inference. Been using it on production for more than 6months and I'm really happy with the results.

The whole problem arises because we can't forbid a function from throwing, and we cant know the type that it's thrown, so what I did is favour the idiomatic way of returning an error, which is returning a rejected "Promise" and if for some reason a function throws, i put the error inside an UnknownError class, with an unknown property.

Here's the library https://github.com/ts-task/task and here is a video explain in it with more detail https://www.youtube.com/watch?v=T7O1T1wmw00

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants