Chouette
view release on metacpan or search on metacpan
$c->task('db')->selectrow_hashref(q{ SELECT * FROM sometable WHERE id = ? },
undef, $id, sub {
my ($dbh, $row) = @_;
die $c->respond($row);
});
Checkout options can be passed after the task name:
$c->task('db', timeout => 5)->selectrow_hashref(...);
See AnyEvent::Task for more details.
EXCEPTIONS
Assuming you are familiar with asynchronous programming, most of
Chouette should feel straightforward. The only thing that might be
unfamiliar is how exceptions are used.
ERROR HANDLING
The first unusual thing about how Chouette uses exceptions is that it
uses them for error conditions, in contrast to many other asynchronous
frameworks.
Most asynchronous frameworks are unable to use exceptions to signal
errors since an error may occur in a callback being run from the event
loop. If this callback throws an exception, there will be nothing to
catch it, except perhaps a catch block installed by the event loop. Even
if the event loop does catch it, it won't know which connection the
exception is for, and therefore won't be able to send a 500 error to
that connection or add a message to that connection's log.
Consider the AnyEvent::DBI library. This is how its error handling
works:
$dbh->exec("SELECT * FROM no_such_table", sub {
my ($dbh, $rows, $rv) = @_;
if ($#_) {
# success
} else {
# failure. error message is in $@
}
});
Even if "exec" failed, the callback still gets called. Whether or not it
succeeded is indicated by its parameters. You can think of this as a
sort of "in-band" signalling. The fact that there was an error, and what
exactly that error was, needs to be indicated by the callback's
parameters in some way. Unfortunately every library does this slightly
differently. Another alternative used by some libraries is to accept 2
callbacks, one of which is called in the success case, and the other in
the failure case.
But with both of these methods, what should the callback do when it is
notified of an error? It can't just "die" because nothing will catch the
exception. With the EV event loop you will see this:
EV: error in callback (ignoring): failure: ERROR: relation "no_such_table" does not exist
Even if you wrap an "eval" or a Try::Tiny "try {} catch {}" around the
code the same thing happens. The try/catch is in effect while installing
the callback, but not when the callback is called.
As a consequence of all this, asynchronous web frameworks usually cannot
indicate errors with exceptions. Instead, they require you to respond to
the client from inside the callback:
$dbh->exec("SELECT * FROM no_such_table", sub {
my ($dbh, $rows, $rv) = @_;
if (!$#_) {
$context->respond_500_error("DB error: $@");
return;
}
# success
});
There are several down-sides to this approach:
* The error must be handled locally in each callback, rather than once
in a catch-all error handler.
* Everywhere an error might occur needs to have access to the context
object. This often requires passing it as an argument around
everywhere.
* You might forget to handle an error (or it might be too inconvenient
so you don't bother) and your success-case code will run on garbage
data.
* Perhaps most importantly, if some unexpected exception is thrown by
your callback (or something that it calls) then the event loop will
receive an exception and nothing will get logged or replied to.
For these reasons, Chouette uses Callback::Frame to deal with
exceptions. The idea is that the exception handling code is carried
around with your callbacks. For instance, this is how you would
accomplish the same thing with Chouette:
my $dbh = $c->task('db');
$dbh->selectrow_arrayref("SELECT * FROM no_such_table", undef, sub {
my ($dbh, $rows) = @_;
# success
# Even if I can die here and it will get routed to the right request!
});
The callback will only be invoked in the success case. If a failure
occurs, an exception will be raised in the dynamic scope that was in
effect when the callback was installed. Because Chouette installs a
"catch" handler for each request, an appropriate error will be sent to
the client and added to the Chouette logs.
Important note: Libraries like AnyEvent::Task (which is what "task" in
the above example uses) are Callback::Frame-aware. This means that you
can pass "sub {}" callbacks into them and they will automatically
convert them to "fub {}" callbacks for you.
( run in 2.351 seconds using v1.01-cache-2.11-cpan-8f98c5d2c55 )