TypeScript: Different types for rejected/fulfilled Promise
TypeScript 1.8.9
I’m trying to understand how to correctly type a rejected promise. I expect the following code to compile.
const bar: Promise<number> =
Promise.resolve(1)
.then(() => Promise.reject<string>(new Error('foo')))
Error:
main.ts(7,7): error TS2322: Type 'Promise<string>' is not assignable to type 'Promise<number>'.
Type 'string' is not assignable to type 'number'.
Is it possible to have different types for a Promise when it is rejected and fulfilled?
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 20 (5 by maintainers)
Links to this issue
Commits related to this issue
- Update WIP - run "npm update" - clean package.json - switch deprecated packages to non-deprecated ones - mainly switch from "tsd" to "typings" NOTE: node.d.ts and require.d.ts have been manually edit... — committed to teekkarispeksi/nappikauppa2 by Blizzara 8 years ago
- Update dependencies - run "npm update" - clean package.json - switch deprecated packages to non-deprecated ones - mainly switch from "tsd" to "typings" NOTE: node.d.ts and require.d.ts have been manu... — committed to teekkarispeksi/nappikauppa2 by Blizzara 8 years ago
@mhegazy why was this issue closed?
I was expecting to find a generic type with two type arguments, e.g.
Promise<TResolved,TRejected>.My use-case is a simple wrapper around an
XMLHttpRequest, which should resolve as a JSON object on success - on error, it should reject with the actualXMLHttpRequestinstance, so the consumer can obtain thestatus,statusText, any returned headers, and whatever else you might need in order to handle the error.I agree with @mindplay-dk that
Promises are better represented with two type arguments. There is potential for more type safety. When usingthenthe resulting promise error type could be a union of the possible errors.And a promise that never rejects could have the type
Promise<A, never>.@heyimalex I do understand that the errors are very dynamic and hard to predict. I still think that defaulting to void and giving the user at least the chance to structure their errors is still valuable (as hard/tedious as it may be to catch + rethrow a specific type). But if I am in the minority here I understand your concerns.
@heyimalex I still really think that there could be some value in having this. Doing
Promise<T, U = any>would state thatUis dynamic and possibly anything, but if the user is determined enough, they can use aUof their own choice. I’m sure this issue will remain closed, but just wanted to comment again in case you’ve had a change of heart.Doesn’t the same apply to any function?
Yes exactly, but you haven’t asserted what types of errors that a function might throw, unlike a
Promise. There is no external contract, unlike return type, to determine what might or might not be thrown by a function. If a function only has a code path that throws, it TypeScript will inferneveras the return type, but makes no contract about the type of error. My comment just above yours highlights where it all goes pete-tong.You can chain the promises, where the error type cannot be inferred while the resolution type can be:
The
Promise<void>type comes from the type definition forPromise.reject:Your original example is using the first overload. Since there is no information available to infer the fulfillment type, it uses
void. The second overload allows you to explicitly state the promise type.Perhaps what you are wanting is for TypeScript to be able to work out that the last promise in the chain is always rejected and therefore the fulfilment value is irrelevant?
It could effectively do this if the
Promise.rejecttype definition was changed to:Which basically says " This will never be fulfiled so it’s effectively compatible with any fulfilment value type since such a value will never be produced."
That might be a specific suggestion worth making to the team. I’m not sure if
voidwas chosen overanyfor some reason overlooked here.@kitsonk @HeyImAlex I’m not sure I understand. It seems to me that adding a second param is strictly better than having a second param that is an
any. The problem of typing unknown runtime exceptions is a general problem, and is distinct from the problem of typing known runtime exceptions.With default type arguments in the 2.3 Release, couldn’t it be argued that the reject type can be defaulted to its current
void, which keeps backward compatibility, and then allow the consumers to specify a second reject type argument?Can you guarantee that a promise adheres to that signature? What if there’s an unexpected exception inside your promise handler?
I just came across this. How should I learn the correct typings for such APIs? Just read the declaration files, or is there are more reader-friendly format published somewhere?
Can this be reopened? I think that disallowing the user to specify the error value is very counterproductive, and with the new release, there should be no breaking changes.
The whole point of TypeScript is to allow static typing, and this is forcing 50% of the promise’s type information to be implicit/dynamic. Considering how widespread Promises are, I think this is worth considering.
The only times that exceptions are typed is in catch blocks and rejected promise handlers, so it’s not really a general problem. Even if you know the type of a rejected promise is
T, because of the possibility of an unknown exception it will always actually beT | any, which is really justany. Unless you can statically tell whether an exception can occur, you can’t tell what the value passed to the rejected promise handler will be and so typing it withTwould not be type safe. There’s no way around it.Admittedly that’s a pretty academic argument, and having typed rejections would be awesome in terms of self documenting promise-based apis.
The
TinPromise<T>refers to the type of the fulfilled value. There is no generic type for the rejection reason. It’s type is hardcoded toanyin the type definitions I’ve seen. E.g. here’s a snippet from'es6-promise.d.ts':So both
TandUrefer to fulfillment value types, and the error reason is typed asany.In your example, since
barhas typePromise<number>, then the final promise in the chain (which is the promise assigned tobar) has to match that type. So just change the last bit toPromise.reject<number>(new Error('foo')))and it compiles.