NAME
Future::Q - a thenable Future like Q module for JavaScript
VERSION
Version 0.020
SYNOPSIS
use Future::Q;
sub async_func_future {
my @args = @_;
my $f = Future::Q->new;
other_async_func( ## This is a regular callback-style async function
args => \@args,
on_success => sub { $f->fulfill(@_) },
on_failure => sub { $f->reject(@_) },
);
return $f;
}
async_func_future()->then(sub {
my @results = @_;
my @processed_values = do_some_processing(@results);
return @processed_values;
})->then(sub {
my @values = @_; ## same values as @processed_values
return async_func_future(@values);
})->then(sub {
warn "Operation finished.\n";
})->catch(sub {
## failure handler
my $error = shift;
warn "Error: $error\n";
});
DESCRIPTION
Future::Q is a subclass of Future. It extends its API with then() and try() etc, which are almost completely compatible with Kris Kowal's Q module for Javascript.
Basically a Future (in a broad meaning) represents an operation (whether it's in progress or finished) and its results. For further information as to what Future is all about, see:
Future - the base class
Promises - based on jQuery and YUI Deferred plug-in
Q module - Javascript module
Terminology of Future States
Any Future::Q object is in one of the following four states.
pending - The operation represented by the Future::Q object is now in progress.
fulfilled - The operation has succeeded and the Future::Q object has its results. The results can be obtained by
get()method.rejected - The operation has failed and the Future::Q object has the reason of the failure. The reason of the failure can be obtained by
failure()method.cancelled - The operation has been cancelled.
The state transition is one-way; "pending" -> "fulfilled", "pending" -> "rejected" or "pending" -> "cancelled". Once the state moves to a non-pending state, its state never changes anymore.
In the terminology of Future, "done" and "failed" are used for "fulfilled" and "rejected", respectively.
You can check the state of a Future::Q with predicate methods is_pending(), is_fulfilled(), is_rejected() and is_cancelled().
then() Method
Using then() method, you can register callback functions with a Future::Q object. The callback functions are executed when the Future::Q object is fulfilled or rejected. You can obtain and use the results of the Future::Q within the callbacks.
The return value of then() method represents the results of the callback function (if it's executed). Since the callback function is also an operation in progress, the return value of then() is naturally a Future::Q object. By calling then() method on the returned Future::Q object, you can chain a series of operations that are executed sequentially.
See the specification of then() method below for details.
Reporting Unhandled Failures
Future::Q warns you when a rejected Future::Q object is destroyed without its failure handled. This is because ignoring a rejected Future::Q is just as dangerous as ignoring a thrown exception. Any rejected Future::Q object must be handled properly.
When a rejected but unhandled Future::Q is destroyed, the reason of the failure is printed through Perl's warning facility. You can capture them by setting $SIG{__WARN__}. The warning messages can be evaluated to strings. (They ARE strings actually, but this may change in future versions)
Future::Q thinks failures of the following futures are "handled".
Futures that
then()orcatch()method has been called on.Futures that are returned by
$on_fulfilledor$on_rejectedcallbacks forthen()method.Subfutures given to
wait_all(),wait_any(),needs_all()andneeds_any()method.
So make sure to call catch() method at the end of any callback chain to handle failures.
I also recommend always inspecting failed subfutures using failed_futures() method in callbacks for dependent futures returned by wait_all(), wait_any(), needs_all() and needs_any(). This is because there may be multiple of failed subfutures. It is even possible that some subfutures fail but the dependent future succeeds.
CLASS METHODS
In addition to all class methods in Future, Future::Q has the following class methods.
$future = Future::Q->new()
Constructor. It creates a new pending Future::Q object.
$future = Future::Q->try($func, @args)
$future = Future::Q->fcall($func, @args)
Immediately executes the $func with the arguments @args, and returns a Future object that represents the result of $func.
fcall() method is an alias of try() method.
$func is a subroutine reference. It is executed with the optional arguments @args.
The return value ($future) is determined by the following rules:
If
$funcreturns a single Future object,$futureis that object.If
$functhrows an exception,$futureis a rejected Future::Q object with that exception. The exception is never rethrown to the upper stacks.Otherwise,
$futureis a fulfilled Future::Q object with the values returned by$func.
If $func is not a subroutine reference, it returns a rejected Future::Q object.
OBJECT METHODS
In addition to all object methods in Future, Future::Q has the following object methods.
$next_future = $future->then([$on_fulfilled, $on_rejected])
Registers callback functions that are executed when $future is fulfilled or rejected, and returns a new Future::Q object that represents the result of the whole operation.
Difference from then() method of Future
Future::Q overrides the then() method of the base Future class. Basically they behave in the same way, but in then() method of Future::Q,
the callback funcions do not have to return a Future object. If they do not, the return values are automatically transformed into a fulfilled Future::Q object.
it will not warn you even if you call the
then()method in void context.
Detailed specification
Below is the detailed specification of then() method.
$on_fulfilled and $on_rejected are subroutine references. When $future is fulfilled, $on_fulfilled callback is executed. Its arguments are the values of the $future obtained by $future->get method. When $future is rejected, $on_rejected callback is executed. Its arguments are the reason of the failure obtained by $future->failure method. Both $on_fulfilled and $on_rejected are optional.
$next_future is a new Future::Q object. In a nutshell, it represents the result of $future and the subsequent execution of $on_fulfilled or $on_rejected callback.
In detail, the state of $next_future is determined by the following rules.
While
$futureis pending,$next_futureis pending.When
$futureis cancelled, neither$on_fulfillednor$on_rejectedis executed, and$next_futurebecomes cancelled.When
$futureis fulfilled and$on_fulfilledisundef,$next_futureis fulfilled with the same values as$future.When
$futureis rejected and$on_rejectedisundef,$next_futureis rejected with the same values as$future.When
$futureis fulfilled and$on_fulfilledis provided,$on_fulfilledis executed. In this case$next_futurerepresents the result of$on_fulfilledcallback (see below).When
$futureis rejected and$on_rejectedis provided,$on_rejectedis executed. In this case$next_futurerepresents the result of$on_rejectedcallback (see below).In the above two cases where
$on_fulfilledor$on_rejectedcallback is executed, the following rules are applied to$next_future.If the callback returns a single Future (call it
$returned_future),$next_future's state is synchronized with that of$returned_future.If the callback throws an exception,
$next_futureis rejected with that exception. The exception is never rethrown to the upper stacks.Otherwise,
$next_futureis fulfilled with the values returned by the callback.
Note that the whole operation can be executed immediately. For example, if $future is already fulfilled, $on_fulfilled callback is executed before $next_future is returned. And if $on_fulfilled callback does not return a pending Future, $next_future is already in a non-pending state.
You can call cancel() method on $next_future. If $future is pending, it is cancelled when $next_future is cancelled. If either $on_fulfilled or $on_rejected is executed and its $returned_future is pending, the $returned_future is cancelled when $next_future is cancelled.
You should not call fulfill() or reject() on $next_future.
$next_future = $future->catch([$on_rejected])
Alias of $future->then(undef, $on_rejected).
$future = $future->fulfill(@result)
Fulfills the pending $future with the values @result.
This method is an alias of $future->done(@result).
$future = $future->reject($exception, @details)
Rejects the pending $future with the $exception and optional @details. $exception must be a scalar evaluated as boolean true.
This method is an alias of fail() method (not die() method).
$is_pending = $future->is_pending()
Returns true if the $future is pending. It returns false otherwise.
$is_fulfilled = $future->is_fulfilled()
Returns true if the $future is fulfilled. It returns false otherwise.
$is_rejected = $future->is_rejected()
Returns true if the $future is rejected. It returns false otherwise.
EXAMPLE
try() and then()
use Future::Q;
## Values returned from try() callback are transformed into a
## fulfilled Future::Q
Future::Q->try(sub {
return (1,2,3);
})->then(sub {
print join(",", @_), "\n"; ## -> 1,2,3
});
## Exception thrown from try() callback is transformed into a
## rejected Future::Q
Future::Q->try(sub {
die "oops!";
})->catch(sub {
my $e = shift;
print $e; ## -> oops! at eg/try.pl line XX.
});
## A Future returned from try() callback is returned as is.
my $f = Future::Q->new;
Future::Q->try(sub {
return $f;
})->then(sub {
print "This is not executed.";
}, sub {
print join(",", @_), "\n"; ## -> a,b,c
});
$f->reject("a", "b", "c");
DIFFERENCE FROM Q
Although Future::Q tries to emulate the behavior of Q module for JavaScript as much as possible, there is difference in some repects.
Future::Q has both roles of "promise" and "deferred" in Q. Currently there is no read-only future like "promise".
Future::Q has the fourth state "cancelled", while promise in Q does not.
In Future::Q, callbacks for
then()method can be executed immediately. This is because Future::Q does not assume any event loop mechanism.In Future::Q, you must pass a truthy value to
reject()method. This is required by the original Future class.
Missing Methods
Some methods in Q module are missing in Future::Q. Some of them worth noting are listed below.
- promise.fail()
-
Future already has
fail()method for a completely different meaning. Usecatch()method instead. - promise.progress(), deferred.notify(), promise.finally(), promise.fin()
-
Progress handlers and "finally" callbacks are interesting features, but they are not supported in this version of Future::Q.
- promise.done()
-
Future already has
done()method for a completely different meaning. There is no corresponding method in this version of Future::Q. - promise.fcall() (object method)
-
Its class method form is enough to get the job done. Use
Future::Q->fcall(). - promise.all(), promise.allResolve()
-
Use
Future::Q->needs_all()andFuture::Q->wait_all()methods, respectively. - deferred.resolve()
-
This is an interesting method, but it's not supported in this version of Future::Q. Call
fulfill()orreject()explicitly instead.
SEE ALSO
ACKNOWLEDGEMENT
Paul Evans, <leonerd at leonerd.org.uk> - author of Future
AUTHOR
Toshio Ito, <toshioito at cpan.org>
LICENSE AND COPYRIGHT
Copyright 2013 Toshio Ito.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.