mirror of
https://github.com/danog/amp.git
synced 2024-12-03 09:57:51 +01:00
Update future docs
This commit is contained in:
parent
49f5a5951a
commit
79c57650ae
@ -1,22 +1,21 @@
|
|||||||
---
|
---
|
||||||
layout: "docs"
|
layout: "docs"
|
||||||
title: "Futures"
|
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.
|
- **Completed successfully**: The future has been completed successfully.
|
||||||
- **Failure**: The promise failed.
|
- **Errored**: The future failed with an exception.
|
||||||
- **Pending**: The promise has not been resolved yet.
|
- **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
|
A successfully completed future is analog to a return value, while an errored future is analog to throwing an exception.
|
||||||
exception.
|
|
||||||
|
|
||||||
Promises are the basic unit of concurrency in asynchronous applications. In Amp they implement the `Amp\Promise`
|
Futures are the basic unit of concurrency in asynchronous applications. These objects should be thought of as
|
||||||
interface. These objects should be thought of as placeholders for values or tasks that might not be complete
|
placeholders for values or tasks that might not be complete immediately.
|
||||||
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
|
```php
|
||||||
doSomething(function ($error, $value) {
|
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
|
- 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.
|
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.
|
callbacks) to be registered.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
doSomething()->onResolve(function ($error, $value) {
|
try {
|
||||||
if ($error) {
|
$value = doSomething()->await();
|
||||||
/* ... */
|
} catch (...) {
|
||||||
} else {
|
/* ... */
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
{:.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.
|
|
||||||
|
|
||||||
## Promise Consumption
|
|
||||||
|
|
||||||
```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
|
We can now write helper functions like [`Amp\Promise\all()`](https://amphp.org/amp/promises/combinators#all) which
|
||||||
most code will not interact with this API directly thanks to [coroutines](../coroutines/), let's take a quick look at
|
subscribe to several of those placeholders and combine them. We don't have to write any complicated code to combine the
|
||||||
the one simple API method exposed on `Amp\Promise` implementations:
|
results of several operations.
|
||||||
|
|
||||||
| Parameter | Callback Signature |
|
## Future Creation
|
||||||
| ------------ | ------------------------------------------ |
|
|
||||||
| `$onResolve` | `function ($error = null, $result = null)` |
|
|
||||||
|
|
||||||
`Amp\Promise::onResolve()` accepts an error-first callback. This callback is responsible for reacting to the eventual
|
Futures can be created in several ways. Most code will use [`Amp\async()`](https://amphp.org/amp/coroutines/)
|
||||||
result represented by the promise placeholder. For example:
|
which takes a function and runs it as coroutine.
|
||||||
|
|
||||||
```php
|
### Immediately Available Results
|
||||||
<?php
|
|
||||||
|
|
||||||
$promise = someFunctionThatReturnsAPromise();
|
Sometimes results are immediately available. This might be due to them being cached, but can also be the case if an
|
||||||
$promise->onResolve(function (Throwable $error = null, $result = null) {
|
interface mandates a `Future` to be returned. In these cases `Future::complete(mixed)` and `Future::error(Throwable)`
|
||||||
if ($error) {
|
can be used to construct an immediately completed `Future`.
|
||||||
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
|
### DeferredFuture
|
||||||
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}
|
{:.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.
|
> 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.
|
||||||
|
|
||||||
`Amp\Deferred` is the abstraction responsible for resolving future values once they become available. A library that
|
`Amp\DeferredFuture` is the abstraction responsible for resolving future values once they become available. A library
|
||||||
resolves values asynchronously creates an `Amp\Deferred` and uses it to return an `Amp\Promise` to API consumers. Once
|
that resolves values asynchronously creates an `Amp\DeferredFuture` and uses it to return an `Amp\Future` to API
|
||||||
the async library determines that the value is ready it resolves the promise held by the API consumer using methods on
|
consumers. Once the library determines that the value is ready, it resolves the `Future` held by the API consumer using
|
||||||
the linked promisor.
|
methods on the linked `DeferredFuture`.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
final class Deferred
|
final class DeferredFuture
|
||||||
{
|
{
|
||||||
public function promise(): Promise;
|
public function getFuture(): Future;
|
||||||
public function resolve($result = null);
|
public function complete(mixed $value = null);
|
||||||
public function fail(Throwable $error);
|
public function error(Throwable $throwable);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `promise()`
|
#### `getFuture()`
|
||||||
|
|
||||||
Returns the corresponding `Promise` instance. `Deferred` and `Promise` are separated, so the consumer of the promise
|
Returns the corresponding `Future` instance. `DeferredFuture` and `Future` are separated, so the consumer of
|
||||||
can't fulfill it. You should always return `$deferred->promise()` to API consumers. If you're passing `Deferred` objects
|
the `Future` can't complete it. You should always return `Future` to API consumers. If you're passing `DeferredFuture`
|
||||||
around, you're probably doing something wrong.
|
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
|
Completes the future with the first parameter as value, otherwise `null`. Instances of `Amp\Future` are not supported;
|
||||||
will wait until the passed promise has been resolved. Invokes all registered `Promise::onResolve()` callbacks.
|
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`
|
Makes the future fail.
|
||||||
argument.
|
|
||||||
|
|
||||||
Here's a simple example of an async value producer `asyncMultiply()` creating a deferred and returning the associated
|
#### Future Example
|
||||||
promise to its API consumer.
|
|
||||||
|
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
|
||||||
<?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;
|
$deferred = new Amp\DeferredFuture;
|
||||||
|
|
||||||
// Resolve the async result one second from now
|
// 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);
|
$deferred->resolve($x * $y);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->getFuture();
|
||||||
}
|
}
|
||||||
|
|
||||||
$promise = asyncMultiply(6, 7);
|
$future = asyncMultiply(6, 7);
|
||||||
$result = Amp\Promise\wait($promise);
|
$result = $future->await();
|
||||||
|
|
||||||
var_dump($result); // int(42)
|
var_dump($result); // int(42)
|
||||||
```
|
```
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
layout: "docs"
|
layout: "docs"
|
||||||
title: "Promise Combinators"
|
title: "Future Combinators"
|
||||||
permalink: "/promises/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()`
|
## `all()`
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ final class DeferredFuture
|
|||||||
/**
|
/**
|
||||||
* Completes the operation with a result value.
|
* 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