NAME
Future::Q - a Future (or Promise or Deferred) like Q module for JavaScript
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.
Future::Q's API and documentation is designed to be self-contained, at least for basic usage of Futures. If a certain function you want is missing in this module, you should refer to "Missing Methods and Features" section and/or Future. (But be prepared because Future has a lot of methods!)
Basically a Future (in a broad meaning) represents an operation (whether it's in progress or finished) and its results. It is also referred to as "Promise" or "Deferred" in other contexts. For further information as to what Future is all about, see:
Future - the base class
Promises - another Future/Promise/Deferred implementation with pretty good documentation
Q - 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.
By default when a rejected but unhandled Future::Q is destroyed, the reason of the failure is printed through Perl's warning facility. This behavior can be modified by setting $OnError
package variable (see below).
Future::Q thinks failures of the following futures are "handled".
Futures that
then()
,catch()
orfinally()
method has been called on.Futures returned by
$on_fulfilled
or$on_rejected
callbacks forthen()
orcatch()
method.Futures returned by
$callback
forfinally()
method.Subfutures given to
wait_all()
,wait_any()
,needs_all()
andneeds_any()
method.Futures given to another Future's
resolve()
method as its single argument.
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.
PACKAGE VARIABLES
You can set the following package variables to change Future::Q's behavior.
$OnError
A subroutine reference called when a rejected but unhandled Future::Q object is destroyed.
$OnError
is called like
$OnError->($warning_message)
The $warning_message
can be evaluated to a human-readable string (It IS a string actually, but this may change in future versions). So you can pass the string to a logger, for example.
my $logger = ...;
$Future::Q::OnError = sub {
my ($warning_message) = @_;
$logger->warn("Unhanlded Future: " . $warning_message);
};
If $OnError
is undef
, which is the default, $warning_message
is printed by the built-in warn()
function. You can capture it by setting $SIG{__WARN__}
.
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
$func
returns a single Future object,$future
is that object.If
$func
throws an exception,$future
is a rejected Future::Q object with that exception. The exception is never rethrown to the upper stacks.Otherwise,
$future
is 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
, which are obtained by $future->get
method. When $future
is rejected, $on_rejected
callback is executed. Its arguments are the reason of the failure, which are 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
$future
is pending,$next_future
is pending.When
$future
is cancelled, neither$on_fulfilled
or$on_rejected
is executed, and$next_future
becomes cancelled.When
$future
is fulfilled and$on_fulfilled
isundef
,$next_future
is fulfilled with the same values as$future
.When
$future
is rejected and$on_rejected
isundef
,$next_future
is rejected with the same values as$future
.When
$future
is fulfilled and$on_fulfilled
is provided,$on_fulfilled
is executed. In this case$next_future
represents the result of$on_fulfilled
callback (see below).@returned_values = $on_fulfilled->(@values)
When
$future
is rejected and$on_rejected
is provided,$on_rejected
is executed. In this case$next_future
represents the result of$on_rejected
callback (see below).@returned_values = $on_rejected->($exception, @detail)
In the above two cases where
$on_fulfilled
or$on_rejected
callback 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_future
is rejected with that exception. The exception is never rethrown to the upper stacks.Otherwise,
$next_future
is 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()
, reject()
, resolve()
etc on $next_future
.
Because then()
method passes the $future
's failure to $on_rejected
callback or $next_future
, $future
's failure becomes "handled", i.e., Future::Q won't warn you if $future
is rejected and DESTROYed.
$next_future = $future->catch([$on_rejected])
Alias of $future->then(undef, $on_rejected)
.
$next_future = $future->finally($callback)
Registers a callback function that is executed when $future
is either fulfilled or rejected. This callback is analogous to "finally" block of try-catch-finally statements found in Java etc.
It returns a new Future::Q object ($next_future
) that keeps the result of the operation.
The mandatory argument, $callback
, is a subroutine reference. It is executed with no arguments when $future
is either fulfilled or rejected.
@returned_values = $callback->()
If $callback
finishes successfully, $next_future
has the same state and values as $future
. That is, if $future
is fulfilled $next_future
becomes fulfilled with the same values, and if $future
is rejected $next_future
becomes rejected with the same failure. In this case the return values of $callback
are discarded.
If $callback
fails, $next_future
is rejected with the failure thrown by $callback
. In this case the values of $future
are discarded.
In detail, the state of $next_future
is determined by the following rules.
When
$future
is pending,$next_future
is pending.When
$future
is cancelled,$callback
is not executed, and$next_future
becomes cancelled.When
$future
is fulfilled or rejected,$callback
is executed with no arguments.$next_future
's state depends on the result of$callback
.If the
$callback
returns a single Future (call it$returned_future
),$next_future
waits for$returned_future
to become non-pending state.When
$returned_future
is pending,$next_future
is pending.When
$returned_future
is fulfilled or cancelled,$next_future
has the same state and values as$future
. In this case, values of$returned_future
are discarded.When
$returned_future
is rejected,$next_future
is rejected with$returned_future
's failure.
If the
$callback
throws an exception,$next_future
is rejected with that exception. The exception is never rethrown to the upper stacks.Otherwise,
$next_future
has the same state and values as$future
. Values returned from$callback
are discarded.
You can call cancel()
method on $next_future
. If $future
is pending, it is cancelled when $next_future
is cancelled. If $callback
returns a single Future and the $returned_future
is pending, the $returned_future
is cancelled when $next_future
is cancelled.
You should not call fulfill()
, reject()
, resolve()
etc on $next_future
.
Because finally()
method passes the $future
's failure to $next_future
, $future
's failure becomes "handled", i.e., Future::Q won't warn you if $future
is rejected and DESTROYed.
$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).
$future = $future->resolve(@result)
Basically same as fulfill()
method, but if you call it with a single Future object as the argument, $future
will follow the given Future's state.
Suppose you call $future->resolve($base_future)
, then
If
$base_future
is pending,$future
is pending. When$base_future
changes its state,$future
will change its state to$base_future
's state with the same values.If
$base_future
is fulfilled,$future
is immediately fulfilled with the same values as$base_future
's.If
$base_future
is rejected,$future
is immediately rejected with the same values as$base_future
's.If
$base_future
is cancelled,$future
is immediately cancelled.
After calling resolve()
, you should not call fulfill()
, reject()
, resolve()
etc on the $future
anymore.
You can call cancel()
on $future
afterward. If you call $future->cancel()
, $base_future
is cancelled, too.
Because $base_future
's state is passed to $future
, $base_future
's failure becomes "handled", i.e., Future::Q won't warn you when $base_future
is rejected and DESTROYed.
$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.
$is_cancelled = $future->is_cancelled()
Returns true if the $future
is cancelled. It returns false otherwise. This method is inherited from Future.
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");
finally()
use Future::Q;
my $handle;
## Suppose Some::Resource->open() returns a handle to a resource (like
## database) wrapped in a Future
Some::Resource->open()->then(sub {
$handle = shift;
return $handle->read_data(); ## Read data asynchronously
})->then(sub {
my $data = shift;
print "Got data: $data\n";
})->finally(sub {
## Ensure closing the resource handle. This callback is called
## even when open() or read_data() fails.
$handle->close() if $handle;
});
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 respects.
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()
andtry()
methods can be executed immediately, while they are always deferred in Q. 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 and Features
Some methods and features 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()
-
Progress handlers are not supported in this version of Future::Q.
- promise.done()
-
Future already has
done()
method for a completely different meaning. Future::Q doesn't need the equivalent of Q'sdone()
method because rejected and unhandled futures are detected in theirDESTROY()
method. See also "Reporting Unhandled Failures". - promise.fcall() (object method)
-
Its class method form is enough to get the job done. Use
Future::Q->fcall()
. - promise.all(), promise.allResolve(), promise.allSettled()
-
Use
Future::Q->needs_all()
andFuture::Q->wait_all()
methods inherited from the original Future class. - promise.inspect()
-
Use predicate methods
is_pending()
,is_fulfilled()
,is_rejected()
andis_cancelled()
. To obtain values from a fulfilled Future, useget()
method. To obtain the reason of the failure from a rejected Future, usefailure()
method. - Q()
-
Use
Future::Q->wrap()
method inherited from the original Future class. - Q.onerror
-
Use
$OnError
package variable, although it is not exactly the same asQ.onerror
. See also "Reporting Unhandled Failures".
SEE ALSO
- Q
-
The JavaScript module that Future::Q tries to emulate.
- Promises/A+
-
"Promises/A+" specification for JavaScript promises. This is the spec that Q implements.
- Future
-
Base class of this module. Future has a lot of methods you may find interesting.
- Future::Utils
-
Utility functions for Futures. Note that the error handling mechanism of Future::Q may not work well with Future::Utils functions. Personally I recommend using CPS for looping asynchronous operations.
- IO::Async::Future
-
Subclass of Future that works well with IO::Async event framework.
- Promises
-
Another promise/deferred/future/whatever implementation. Its goal is to implement Promises/A+ specification. Because Q is also an implementation of Promises/A+, Promises and Q (and Futuer::Q) are very similar.
- AnyEvent::Promises
-
Another port of Q (implementation of Promises/A+) in Perl. It depends on AnyEvent.
- AnyEvent::Promise
-
A simple Promise used with AnyEvent condition variables. Apparently it has nothing to do with Promises/A+.
[AnyEvent::Promise] [Future] -\ +-- [Future::Q] [Promises/A+] -- [Q] -/ -- [Promises] -- [AnyEvent::Promises]
BUGS AND FEATURE REQUESTS
Please report bugs and feature requests to my Github issues https://github.com/debug-ito/Future-Q/issues
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.