Yes, it is really hard. This is a really good question that you are asking here.
Let me start with
Yes, it can be printed
It is quite possible to create a type of promise that takes into account exceptions. When I implemented the promise library in a typed language, I started with the type Promise<T,E> and only later returned to Promise<T> - it worked, but it was not fun. You have a name.
Checked Exceptions
What you are actually asking for here are the exceptions that need to be checked - this is a function that should declare the type of exceptions that it can throw - there are languages ​​that really do this for exceptions ... there is a language that does it - Java . In Java, when you have a method that can throw an exception (except for RuntimeException), it should declare it:
public T foo() throws E {}
See in Java - both the return type and the error type are part of the method signature. This is a controversial choice, and many people find it tedious. This is very unpopular among developers of other languages ​​because it forces you to write a lot of cool code.
Imagine you have a function that returns a promise that makes a db connection to get the url, makes a web request and writes it to a file. The promise equivalent to what you have in Java is something like this:
Promise<T, FileAccessError | DatabaseError | WebRequestError | WebConnectionError | TypeError>
It's not very fun to type many times, so the types of exceptions in these languages ​​(like C #) are usually implicit. If you like this stylistic choice, you should definitely do it. This is just not entirely trivial: the type of promise is already quite complex:
then<T,U> :: Promise<T> -> T -> Promise<U> | U -> Promise<U>
This is what then does — it requires a promise of type T and a callback that takes T and returns a value (U) or a promise for a value (Promise) - and returns Promise (U-turn and transform). The actual type is even more complex since it has a second rejection argument - and both arguments are optional:
then<T,U> :: Promise<T> -> (T -> Promise<U> | U) | null) -> ((Promise<T> -> any -> Promise<U> | U) | null) -> Promise<U>
If you add error handling, it will become really “funny”, since all these steps now have an additional error path:
then<T,E,U,E2> :: Promise<T,E> -> (T -> Promise<U, E2> | U) | null -> (E -> Promise<U, E2> | U) | null -> Promise<U>
In principle - then now has 4 types of parameters that people usually want to avoid :) This is quite possible, although it’s up to you.