.. | ||
combinators.md | ||
miscellaneous.md | ||
README.md | ||
transformation.md |
Promises
@TODO: Fix generator references once coroutines docs are finished.
The basic unit of concurrency in Amp applications is the Amp\Promise
. These objects should be thought of as placeholders for values or tasks that aren't yet complete. By using placeholders we're able to reason about the results of concurrent operations as if they were already complete variables.
Note
Amp's
Promise
interface does not conform to the "Thenables" abstraction common in JavaScript promise implementations. Chaining.then()
calls is a suboptimal method for avoiding callback hell in a world with generator coroutines. Instead, Amp utilizes PHP generators to "synchronize" concurrent task execution.However, as ReactPHP is another wide-spread implementation, we also accept any
React\Promise\PromiseInterface
where we accept instances ofAmp\Promise
. In case of custom implementations not implementingReact\Promise\PromiseInterface
,Amp\adapt
can be used to adapt any object having athen
ordone
method.
Promise Consumption
interface Promise {
public function when(callable $onResolve);
}
In its simplest form the Amp\Promise
aggregates callbacks for dealing with computational results once they eventually resolve. While most code will not interact with this API directly thanks to the magic of Generators, let's take a quick look at the one simple API method exposed on Amp\Promise
implementations:
Method | Callback Signature |
---|---|
when | function ($error = null, $result = null) |
Amp\Promise::when()
accepts an error-first callback. This callback is responsible for reacting to the eventual result of the computation represented by the promise placeholder. For example:
<?php
$promise = someFunctionThatReturnsAPromise();
$promise->when(function (Throwable $error = null, $result = null) {
if ($error) {
printf(
"Something went wrong:\n%s\n",
$error->getMessage()
);
} else {
printf(
"Hurray! Our result is:\n%s\n",
print_r($result, true)
);
}
});
Those familiar with JavaScript code generally reflect that the above interface quickly devolves into "callback hell", and they're correct. We will shortly see how to avoid this problem in the Generators section.
Promise Creation
Amp\Deferred
is the abstraction responsible for resolving future values once they become available. A library that resolves values asynchronously creates an Amp\Deferred
and uses it to return an Amp\Promise
to API consumers. Once the async library determines that the value is ready it resolves the promise held by the API consumer using methods on the linked promisor.
final class Deferred {
public function promise(): Promise;
public function resolve($result = null);
public function fail($error);
}
promise()
Returns the corresponding Promise
instance. Deferred
and Promise
are separated, so the consumer of the promise can't fulfill it.
resolve()
Resolves the promise with the first parameter as value, otherwise null
. If a Amp\Promise
is passed, the resolution will wait until the passed promise has been resolved. Invokes all registered Promise::when()
callbacks.
fail()
Makes the promise fail. Invokes all registered Promise::when()
callbacks with the passed Throwable
as $error
argument.
Here's a simple example of an async value producer asyncMultiply()
creating a deferred and returning the associated promise to its API consumer.
<?php // Example async producer using promisor
use Amp\Loop;
function asyncMultiply($x, $y) {
// Create a new promisor
$deferred = new Amp\Deferred;
// Resolve the async result one second from now
Loop::delay($msDelay = 1000, function () use ($deferred, $x, $y) {
$deferred->resolve($x * $y);
});
return $deferred->promise();
}
$promise = asyncMultiply(6, 7);
$result = Amp\wait($promise);
var_dump($result); // int(42)
Success and Failure
Sometimes values are immediately available. This might be due to them being cached, but can also be the case if an interface mandates a promise to be returned to allow for async I/O, but the specific implementation being always having directly the result available. In these cases Amp\Success
and Amp\Failure
can be used to construct a immediately resolved promise. Amp\Success
accepts a resolution value. Amp\Failure
accepts an exception as failure reason.