-
Notifications
You must be signed in to change notification settings - Fork 3
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
Add PromiseErrorHandler to decouple the specification #19
Conversation
I feel like the following interface might be better since it's idempotent and would prevent people from having to hold onto handler ids in order to remove handlers later: final class PromiseErrorHandler
{
public static function setHandler($id, callable $handler);
public static function removeHandler($id);
} Unless I'm mistaken, removing the dependency on |
@joshdifabio With that API you need unique IDs. No, not at all. An implementation can choose to work only when a Why do you think it's important to guarantee that callbacks are always called asynchronously? |
Sure, but that's pretty easy. They don't need to be ints.
But it permits synchronous promise resolution, right? Is everyone happy with that? |
Until someone chooses the same rather generic identifier. I guess most handlers won't clean up anyway, so a
What's the problem you see? We already do that in Amp for already resolved promises. This is also not changed by this PR. |
@joshdifabio Where does having fixed identifiers provide a benefit? It just adds global state to something dynamic (dynamic as in there can be multiple handlers), I'd say? @kelunik Does any default behavior make sense? Maybe an E_USER_ERROR (yeah, a real fatal this time) that Promises are used with no error handler active? Also @kelunik can we rename it to just "remove" and "add"? Handler is redundant in method name |
Generally I think it's best if methods which change state don't return any value. In this case that's easily achievable which is why I made the suggestion, but I don't think it's important so I won't bang on about it!
I don't think it's necessarily a problem but I'm curious whether everyone is happy with allowing synchronous promise resolution. It's not exactly the norm in the JS world afaik. See an old ReactPHP discussion on the topic: reactphp/promise#4 |
I'd say the opposite. With handlers identified by auto-generated ID's the ID has to be stored in some kind of global object in order for the handler to be removed later. With a manually specified ID this isn't necessary as a simple constant can be used to set and later remove the handler. I tend to believe that the best functions are either pure side-effect-free functions, commands (i.e. they change state but return nothing), or queries. (Note that this isn't original thinking on my part.) Sometimes it's necessary to compromise on this but often it's not. Still, I don't think this is very important. |
@joshdifabio I pretty much see: $id = PromiseErrorHandler::add($handler);
Loop::execute(...);
PromiseErrorHandler::remove($id); Not sure where we'd have to store it in any global state here. |
I don't think this is worth the time we're spending discussing it. Go with what you think is best. |
I agree with @bwoebi, triggering a I also agree that handler is redundant in the method names. |
It looks good @kelunik nice work 👍 But I agree with @trowski and @bwoebi we need to let errors bubble up. Since this is a last resort handler we can safely trigger an |
Yes, I thought about doing an |
@bwoebi @WyriHaximus I guess it should be ...just that we can only throw |
…o handler has been registered
@kelunik P.S. My two cents about |
@joshdifabio Could you open another issue for that one? |
@WyriHaximus |
@kelunik it is, but imho it is the application using this pakackage responsibility to handle that log and the exit not ours. |
Sure, we just add an emergency handler if nothing else is registered. Note that the error advising you to register at least one handler only appears if you had an error somewhere yet. |
You're latest changes are perfect 👍 . |
I moved the error handler into the |
If you prefix something with "Promise", it very much is not pollution - but I'm also fine with it being in a Promise namespace instead. |
Done. See #20 |
I've been thinking about this, I'm not sure about being able to install multiple handlers. I'm thinking we should rather scope our Promise error handlers like we are scoping Loop::execute(). As you typically want your handler to be invoked (e.g. log errors inside your nested event loop to somewhere else) and not the original handler. We have a reset() method maybe, but that's only fine when we aren't planning to resume the original loop afterwards. Or do you have any concrete reason why we need multiple handlers; ultimately the event loop also only has a single handler. |
@bwoebi For the event loop, multiple handlers don't make sense, as those handlers are either rethrow or be done with it. This handler is for pure notification, it doesn't matter how many callbacks are notified. We might have a default handler, that just logs these exceptions, but there might be another handler during bootstrap that makes startup fail entirely if there's already such an error in the bootstrap process. That said, scoping might be fine as well. |
Isn't it the same here? Quoting from Loop::setErrorHandler:
If it doesn't rethrow, the loop just continues. Same here:
If we don't rethrow, we're also done with it. What do I misunderstand here? If you want that another handler in bootstrap process, scoping will totally satisfy this. No need for multiple handlers there. If you say it might be fine as well, then please go with that. |
A handler here explicitly MUST NOT throw, while it's just fine in a loop, because an exception there just bubbles up to |
Uh, I wouldn't say that. It can deliberately throw, knowing that it will lead to a program abortion. Ultimately, exceptions bubbling up to Loop::execute() usually lead to the program being aborted too... |
No, they shouldn't. They should log the error themselves and exit cleanly with a proper exit code. |
It makes no difference in the end result, the program is aborted anyways. |
@bwoebi After giving it some thought, scoping doesn't really make sense here. At least stacking those scopes does not. As we're using cooperative multitasking here, there will be other promises resolved from an "outer" scope if you try to "scope" your handler in a specific place. But I guess we could remove the handlers and only allow one single handler at a time, which is set by the application. |
Makes sense. Thus the API would be |
Any reason to return the previous one? |
@kelunik to be able to reset it to the old one afterwards, e.g. if you opened a new loop context. |
Is it fair to say that the vast majority of promise consumers won't need to use this error handler? (Setting the error handler will probably be a once-per-application thing. Most of the time we just want to call With that in mind, would it make sense to create a separate package which contains the error handler and any other code which is only needed by promise implementors and application bootstrapping code? I'm thinking about future cases where we want to change error handling code, or any other auxiliary functionality we add to this package; it would be a shame for that to represent an API change given that virtually all consumers of this spec wouldn't be interested in those changes. |
I have changed the patch to allow only one single handler. @WyriHaximus @joshdifabio @bwoebi Could you review please? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Yes, looks good. |
*/ | ||
public static function set(callable $onError = null) | ||
{ | ||
self::$callback = $onError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please return the old error handler here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need that? There should be only one handler per application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To temporarily overwrite it, e.g. in a separate loop context.
Is there any harm in providing the possibility at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do so, we should do the same for the loop error handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, we should do that there too. [I honestly thought we already were doing that.]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Removes the need for an event loop and decouples the two specifications.
It can be discussed whether there should be another static method
PromiseErrorHandler::reset()
that just removes all callbacks.Resolves #18. Relates to #17, async-interop/event-loop#113.