-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
tap analog for catch #589
Comments
Signature for the declare class Promise<T> {
tapError<U>(ErrorClass, handler:(reason: any) => U|Promise<U>): Promise<T>;
} |
I think this is possibly a duplicate |
I suspect that it can be implemented via
At least it is not implemented. Is it? |
@petkaantonov that is correct. Duplicate of #517 |
@benjamingr technically it resembles the duplicate. However here is absolutely different motivation and strong indications that it is a lacking functionality. Please answer to the one question: // Let's consider a classical signature for `catch`
Promise<T>::catch<U>((reason: any) => U|Thenable<U>) => Promise<U>
// wrap :: t -> [t]
function wrap<T>(value: T): Array<T> {
return [value];
}
// unwrap :: [t] -> t
function wrap<T>(value: Array<T>): T {
return value[0];
}
// (???) What signature should have the following function
function logAndThrow(e) {
console.log(e);
throw e;
}
// ... to be correctly used in the following chain (there should be no type-errors)
var p:Promise<string> = Promise.resolve("test")
.then(wrap)
.then(wrap)
.then(wrap)
.catch(logAndThrow)
.then(unwrap)
.then(unwrap)
.then(unwrap)
.then(wrap)
.catch(logAndThrow)
.then(unwrap) @spion - you are welcome to solve this nice type-theory puzzle too :) |
@Artazor log and throw is an anti pattern that is not embraced by bluebird, that was already discussed in the duplicate thread |
@Artazor your type annotations are weird, sometimes you use Haskell and sometimes you use :: for prototype method but ok. If you want implicit side effects (the logging, the type signature is simply:
In "Haskell":
It's quite trivial, it takes a Promise and a string message and returns a Promise over the same resolution value. Although, it's worth mentioning your |
Sorry, my bad -) I've tried to use Haskell in comments only as a clarification. I'm with you against logAndThrow in the catch-all style. But my question was not about logging. I'm talking about correct typing. var x:Promise<string> = someRequest()
.catch(LowLevelError, e => throw new BusinessError(e))
.then(res => res.stringField) // fails to compile Is it a valid use-case? |
I just want to point out that sometimes inability to assign a correct type to a function means that the function is cluttered with several responsibilities and may be split into several functions with better signatures/implementations/semantics. Here I suspect that
The first one definitely should affect the signature of the resulting Promise |
In fact I would be happy if var x:Promise<string> = someRequest()
.catchThrow(LowLevelError, e => new BusinessError(e))
.then(res => res.stringField) |
It's perfectly possible to type The reason people don't do it is because people hate checked exceptions and this is effectively the same thing. I talk about this here: http://stackoverflow.com/questions/29286018/typescript-typings-for-failure-reason-in-various-promises-implementations/29287864#29287864 although the types are somewhat wrong for didactic reasons. Additionally we're never breaking |
Also, JS is not Haskell :) |
@benjamingr thank you for your response! I know about checked exceptions. And I've read you answer on StackOverflow. Unfortunately, Nevertheless, to be more relevant to the real world in your type system there should be type unions Thus, we may try to start with the following type: catch :: (P t e) -> (e' -> (s ^ e'')) -> P s (e - e' + e'') But this still is not a correct type either. The core problem for the The only correct typing for catch :: (P t e) -> (e' -> (t ^ e'')) -> P t (e - e' + e'') It means that to be semantically correct you should never change the type of the success in a catch handler. Expressed in TypeScript it looks like: declare class Promise<T> {
catch(handler: (reason: any) => T|Promise<T>): Promise<T>
} I'm working on Bluebird.d.ts for the v 3.0 (I'll publish it soon, I hope). And in fact I'd place such a constraint there. I suspect that all popular Promise library d.ts definitions are vulnerable to this semantic error. Because it is an inherent problem of the Promise.catch. At the same time, people write correct programs with semantically incorrect tools. That is why I've proposed the tapError. It would be a semantically correct version of the Also, want to hear from @petkaantonov and @spion |
Putting the type of exception something might throw is exactly what checked exceptions means. Also, I'm sorry for being annoying but can you please use standard type notation? I don't care if you go with Java, c++ or Haskell or whatever but this syntax is weird.
No, it just means that the return type of
Yes, that's what it can do right now. This is why the parameter of the return value is a union type and not just T.
No, but it does sound like a good idea, I don't think I've ever changed the return type and it's a good sanity check. At the moment this is not the contract at all but part of typing is that we can enforce stricter contracts than what the language allows. Just because you can pass a promise and not a function to Note, that while your version doesn't imply it |
AFAIK TypeScript can models this properly now with union types. Method would be declare class Promise<T> {
catch<U>(handler: (reason: any) => U|Promise<U>): Promise<T|U>
} If |
You are right about But as I see it's not reflected in
and possibly other libraries declare class Promise<T> {
catch<U>(
handler: (reason: any) => U | Promise<U>
): Promise<T|U>;
then<U>(
fulfilled?: (result: T) => U | Promise<U>,
rejected?: (reason: any) => U | Promise<U>
): Promise<U>
} Unfortunately, this signature spoils the result. promiseOfString.catch(ApiError, function(e) {
throw new BusinessError(e);
}); will have type that is why I wanted for Possibly it is an issue with TypeScript itself. It should have a special bottom type for the result of functions that always throw. And it should be a unit against the type union. i.e. ( bottom | T ) = ( T | bottom ) = T Probably it would be an ideal solution. |
You're right - I fogrot about the pathological case of no return value :D Whoops. |
Just created an issue in microsoft/TypeScript#3076 - any correction/justification? |
Do you mean that I can throw thenables? And they will be unwrapped at the catch side? I've thought catch doesn't perform thenable assimilation from the upstream. Promise.reject(Promise.reject(new Error())).catch(function(e){
console.log(e.constructor.name); // it is "Promise" not an "Error"
}) Or have I incorrectly interpreted your comment? |
You can throw literally anything (but in 3.0 this gives you a warning if you throw non-errors), thrown thenables or promises are not unwrapped |
@petkaantonov exactly, I've just wanted to clarify what @benjamingr has meant |
You can return rejected thenables whose error state will be assumed. That's what I meant. |
Oh, I know that, just not included into the signature for the sake of simplicity.
My odd notation came from my abstract algebra background, where at the beginning of any article, report, etc., we always defined a syntax for a following lemmas and proofs. I've just used the following system for (possibly high order) types: τ ::= tcon | α | (τ τ) | (Λα.τ) | (τ → τ) | (τ × τ) | ⊤ | ⊥ | (τ ⋂ τ) | (τ ⋃ τ) | (!τ) | (τ ^ τ) where
This system has usual parentheses elimination conventions and two aliases:
Checked exceptions (τresult ^ τerror) have two simple properties:
that makes (τ ^ τ) associative. (there are more rules, of course) My apologies for using this notation before defining! -) |
What about
.tapError()
with the same signature as.catch
(with filters) but the following semantics? (analog to #116)Please understand the motivation (!)
It is motivated by the TypeScript usage (@spion you are welcome to comment)
When you write a catch handler only for logging purposes you never return and always throw, but type signature of resulting promise is completely vanished. e.g.
the problem
failed attempt
It fails with
Technically this approach fails only because of microsoft/TypeScript#1613 and will work as soon as static flow analysis will be implemented inTypeScript. However I suppose that it is wrong to specify a return type for this handler. It is not intended to return.
ugly yet successfull
proposed
Does it sound reasonable?
The text was updated successfully, but these errors were encountered: