mirror of
https://github.com/danog/amp.git
synced 2024-12-02 09:27:46 +01:00
Update future docs
This commit is contained in:
parent
49f5a5951a
commit
79c57650ae
@ -1,22 +1,21 @@
|
||||
---
|
||||
layout: "docs"
|
||||
title: "Futures"
|
||||
permalink: "/promises/"
|
||||
permalink: "/futures/"
|
||||
---
|
||||
A `Promise` is an object representing the eventual result of an asynchronous operation. There are three states:
|
||||
A `Future` is an object representing the eventual result of an asynchronous operation. There are three states:
|
||||
|
||||
- **Success**: The promise resolved successfully.
|
||||
- **Failure**: The promise failed.
|
||||
- **Pending**: The promise has not been resolved yet.
|
||||
- **Completed successfully**: The future has been completed successfully.
|
||||
- **Errored**: The future failed with an exception.
|
||||
- **Pending**: The future is still pending.
|
||||
|
||||
A successful resolution is like returning a value in synchronous code while failing a promise is like throwing an
|
||||
exception.
|
||||
A successfully completed future is analog to a return value, while an errored future is analog to throwing an exception.
|
||||
|
||||
Promises are the basic unit of concurrency in asynchronous applications. In Amp they implement the `Amp\Promise`
|
||||
interface. These objects should be thought of as placeholders for values or tasks that might not be complete
|
||||
immediately.
|
||||
Futures are the basic unit of concurrency in asynchronous applications. These objects should be thought of as
|
||||
placeholders for values or tasks that might not be complete immediately.
|
||||
|
||||
Another way to approach asynchronous APIs is using callbacks that are passed when the operation is started.
|
||||
Another way to approach asynchronous APIs is using callbacks that are passed when the operation is started and called
|
||||
once it completes:
|
||||
|
||||
```php
|
||||
doSomething(function ($error, $value) {
|
||||
@ -35,151 +34,91 @@ The callback approach has several drawbacks.
|
||||
- An explicit callback is required as input parameter to the function, and the return value is simply unused. There's no
|
||||
way to use this API without involving a callback.
|
||||
|
||||
That's where promises come into play. They're simple placeholders that are returned and allow a callback (or several
|
||||
That's where futures come into play. They're simple placeholders that are returned and allow a callback (or several
|
||||
callbacks) to be registered.
|
||||
|
||||
```php
|
||||
doSomething()->onResolve(function ($error, $value) {
|
||||
if ($error) {
|
||||
/* ... */
|
||||
} else {
|
||||
try {
|
||||
$value = doSomething()->await();
|
||||
} catch (...) {
|
||||
/* ... */
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This doesn't seem a lot better at first sight, we have just moved the callback. But in fact this enabled a lot. We can
|
||||
now write helper functions like [`Amp\Promise\all()`](https://amphp.org/amp/promises/combinators#all) which subscribe to
|
||||
several of those placeholders and combine them. We don't have to write any complicated code to combine the results of
|
||||
several callbacks.
|
||||
We can now write helper functions like [`Amp\Promise\all()`](https://amphp.org/amp/promises/combinators#all) which
|
||||
subscribe to several of those placeholders and combine them. We don't have to write any complicated code to combine the
|
||||
results of several operations.
|
||||
|
||||
But the most important improvement of promises is that they allow
|
||||
writing [coroutines](https://amphp.org/amp/coroutines/), which completely eliminate the need for _any_ callbacks.
|
||||
## Future Creation
|
||||
|
||||
Coroutines make use of PHP's generators. Every time a promise is `yield`ed, the coroutine subscribes to the promise and
|
||||
automatically continues it once the promise resolved. On successful resolution the coroutine will send the resolution
|
||||
value into the generator using [`Generator::send()`](https://secure.php.net/generator.send). On failure it will throw
|
||||
the exception into the generator using [`Generator::throw()`](https://secure.php.net/generator.throw). This allows
|
||||
writing asynchronous code almost like synchronous code.
|
||||
Futures can be created in several ways. Most code will use [`Amp\async()`](https://amphp.org/amp/coroutines/)
|
||||
which takes a function and runs it as coroutine.
|
||||
|
||||
### Immediately Available Results
|
||||
|
||||
Sometimes results are immediately available. This might be due to them being cached, but can also be the case if an
|
||||
interface mandates a `Future` to be returned. In these cases `Future::complete(mixed)` and `Future::error(Throwable)`
|
||||
can be used to construct an immediately completed `Future`.
|
||||
|
||||
### DeferredFuture
|
||||
|
||||
{:.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 as described above.
|
||||
>
|
||||
> However, as ReactPHP is another wide-spread implementation, we also accept any `React\Promise\PromiseInterface` where we accept instances of `Amp\Promise`. In case of custom implementations not implementing `React\Promise\PromiseInterface`, `Amp\Promise\adapt()` can be used to adapt any object having a `then` or `done` method.
|
||||
> The `DeferredFuture` API described below is an advanced API that many applications probably don't need.
|
||||
> Use [`Amp\async()`](https://amphp.org/amp/coroutines/) or [combinators](https://amphp.org/amp/futures/combinators) instead where possible.
|
||||
|
||||
## Promise Consumption
|
||||
`Amp\DeferredFuture` is the abstraction responsible for resolving future values once they become available. A library
|
||||
that resolves values asynchronously creates an `Amp\DeferredFuture` and uses it to return an `Amp\Future` to API
|
||||
consumers. Once the library determines that the value is ready, it resolves the `Future` held by the API consumer using
|
||||
methods on the linked `DeferredFuture`.
|
||||
|
||||
```php
|
||||
interface Promise {
|
||||
public function onResolve(callable $onResolve);
|
||||
}
|
||||
```
|
||||
|
||||
In its simplest form the `Amp\Promise` aggregates callbacks for dealing with results once they eventually resolve. While
|
||||
most code will not interact with this API directly thanks to [coroutines](../coroutines/), let's take a quick look at
|
||||
the one simple API method exposed on `Amp\Promise` implementations:
|
||||
|
||||
| Parameter | Callback Signature |
|
||||
| ------------ | ------------------------------------------ |
|
||||
| `$onResolve` | `function ($error = null, $result = null)` |
|
||||
|
||||
`Amp\Promise::onResolve()` accepts an error-first callback. This callback is responsible for reacting to the eventual
|
||||
result represented by the promise placeholder. For example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$promise = someFunctionThatReturnsAPromise();
|
||||
$promise->onResolve(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"](http://callbackhell.com/), and they're correct. We will shortly see how to avoid this problem in
|
||||
the [coroutines](../coroutines/README.md) section.
|
||||
|
||||
## Promise Creation
|
||||
|
||||
Promises can be created in several different ways. Most code will
|
||||
use [`Amp\call()`](https://amphp.org/amp/coroutines/helpers#call) which takes a function and runs it as coroutine if it
|
||||
returns a `Generator`.
|
||||
|
||||
### 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 always having the
|
||||
result directly available. In these cases `Amp\Success` and `Amp\Failure` can be used to construct an immediately
|
||||
resolved promise. `Amp\Success` accepts a resolution value. `Amp\Failure` accepts an exception as failure reason.
|
||||
|
||||
### Deferred
|
||||
|
||||
{:.note}
|
||||
> The `Deferred` API described below is an advanced API that many applications probably don't need. Use [`Amp\call()`](https://amphp.org/amp/coroutines/helpers#call) or [promise combinators](https://amphp.org/amp/promises/combinators) instead where possible.
|
||||
|
||||
`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.
|
||||
|
||||
```php
|
||||
final class Deferred
|
||||
final class DeferredFuture
|
||||
{
|
||||
public function promise(): Promise;
|
||||
public function resolve($result = null);
|
||||
public function fail(Throwable $error);
|
||||
public function getFuture(): Future;
|
||||
public function complete(mixed $value = null);
|
||||
public function error(Throwable $throwable);
|
||||
}
|
||||
```
|
||||
|
||||
#### `promise()`
|
||||
#### `getFuture()`
|
||||
|
||||
Returns the corresponding `Promise` instance. `Deferred` and `Promise` are separated, so the consumer of the promise
|
||||
can't fulfill it. You should always return `$deferred->promise()` to API consumers. If you're passing `Deferred` objects
|
||||
around, you're probably doing something wrong.
|
||||
Returns the corresponding `Future` instance. `DeferredFuture` and `Future` are separated, so the consumer of
|
||||
the `Future` can't complete it. You should always return `Future` to API consumers. If you're passing `DeferredFuture`
|
||||
objects around, you're probably doing something wrong.
|
||||
|
||||
#### `resolve()`
|
||||
#### `complete()`
|
||||
|
||||
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::onResolve()` callbacks.
|
||||
Completes the future with the first parameter as value, otherwise `null`. Instances of `Amp\Future` are not supported;
|
||||
Use `Future::await()` before calling `DeferredFuture::complete()` in such cases.
|
||||
|
||||
#### `fail()`
|
||||
#### `error()`
|
||||
|
||||
Makes the promise fail. Invokes all registered `Promise::onResolve()` callbacks with the passed `Throwable` as `$error`
|
||||
argument.
|
||||
Makes the future fail.
|
||||
|
||||
Here's a simple example of an async value producer `asyncMultiply()` creating a deferred and returning the associated
|
||||
promise to its API consumer.
|
||||
#### Future Example
|
||||
|
||||
Here's a simple example of an async value producer `asyncMultiply()` creating a `DeferredFuture` and returning the
|
||||
associated `Future` to its API consumer.
|
||||
|
||||
```php
|
||||
<?php // Example async producer using promisor
|
||||
<?php // Example async producer using DeferredFuture
|
||||
|
||||
use Amp\Loop;
|
||||
use Revolt\EventLoop;
|
||||
|
||||
function asyncMultiply($x, $y)
|
||||
function asyncMultiply(int $x, int $y): Future
|
||||
{
|
||||
// Create a new promisor
|
||||
$deferred = new Amp\DeferredFuture;
|
||||
|
||||
// Resolve the async result one second from now
|
||||
Loop::delay($msDelay = 1000, function () use ($deferred, $x, $y) {
|
||||
EventLoop::delay(1, function () use ($deferred, $x, $y) {
|
||||
$deferred->resolve($x * $y);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
return $deferred->getFuture();
|
||||
}
|
||||
|
||||
$promise = asyncMultiply(6, 7);
|
||||
$result = Amp\Promise\wait($promise);
|
||||
$future = asyncMultiply(6, 7);
|
||||
$result = $future->await();
|
||||
|
||||
var_dump($result); // int(42)
|
||||
```
|
||||
|
@ -1,9 +1,9 @@
|
||||
---
|
||||
layout: "docs"
|
||||
title: "Promise Combinators"
|
||||
permalink: "/promises/combinators"
|
||||
title: "Future Combinators"
|
||||
permalink: "/futures/combinators"
|
||||
---
|
||||
Multiple promises can be combined into a single promise using different functions.
|
||||
Multiple futures can be combined into a single future using different functions.
|
||||
|
||||
## `all()`
|
||||
|
||||
|
@ -22,11 +22,11 @@ final class DeferredFuture
|
||||
/**
|
||||
* Completes the operation with a result value.
|
||||
*
|
||||
* @param T $result Result of the operation.
|
||||
* @param T $value Result of the operation.
|
||||
*/
|
||||
public function complete(mixed $result = null): void
|
||||
public function complete(mixed $value = null): void
|
||||
{
|
||||
$this->state->complete($result);
|
||||
$this->state->complete($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user