diff --git a/docs/futures/README.md b/docs/futures/README.md index fbadabc..46e6c3a 100644 --- a/docs/futures/README.md +++ b/docs/futures/README.md @@ -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 { - /* ... */ - } -}); -``` - -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); +try { + $value = doSomething()->await(); +} catch (...) { + /* ... */ } ``` -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: +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. -| Parameter | Callback Signature | -| ------------ | ------------------------------------------ | -| `$onResolve` | `function ($error = null, $result = null)` | +## Future Creation -`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: +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. -```php -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) - ); - } -}); -``` +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`. -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 +### DeferredFuture {:.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 -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. +`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 -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 -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) ``` diff --git a/docs/futures/combinators.md b/docs/futures/combinators.md index 791418a..3279a56 100644 --- a/docs/futures/combinators.md +++ b/docs/futures/combinators.md @@ -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()` diff --git a/src/DeferredFuture.php b/src/DeferredFuture.php index 633e63f..9f85e95 100644 --- a/src/DeferredFuture.php +++ b/src/DeferredFuture.php @@ -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); } /**