1
0
mirror of https://github.com/danog/amp.git synced 2024-12-11 17:09:40 +01:00

Update documentation

This commit is contained in:
Niklas Keller 2021-12-03 00:09:53 +01:00
parent 1a2be8f2b2
commit d70896df73
8 changed files with 105 additions and 178 deletions

View File

@ -1,5 +1,7 @@
--- ---
layout: docs title: Cancellation permalink: /cancellation/ layout: "docs"
title: "Cancellation"
permalink: "/cancellation/"
--- ---
Amp provides a `Cancellation` primitive to allow the cancellation of operations. Amp provides a `Cancellation` primitive to allow the cancellation of operations.

View File

@ -1,102 +1,40 @@
--- ---
layout: docs layout: "docs"
title: Coroutines title: "Coroutines"
permalink: /coroutines/ permalink: "/coroutines/"
--- ---
Coroutines are interruptible functions. In PHP they can be implemented using [generators](http://php.net/manual/en/language.generators.overview.php). Coroutines are interruptible functions. In PHP they can be implemented using [Fibers](https://wiki.php.net/rfc/fibers).
While generators are usually used to implement simple iterators and yielding elements using the `yield` keyword, Amp uses `yield` as interruption points. When a coroutine yields a value, execution of the coroutine is temporarily interrupted, allowing other tasks to be run, such as I/O handlers, timers, or other coroutines.
```php
// Fetches a resource with Artax and returns its body.
$promise = Amp\call(function () use ($http) {
try {
// Yield control until the generator resolves
// and return its eventual result.
$response = yield $http->request("https://example.com/");
$body = yield $response->getBody();
return $body;
} catch (HttpException $e) {
// If promise resolution fails the exception is
// thrown back to us and we handle it as needed.
}
});
```
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 that no callbacks need to be registered to consume promises and errors can be handled with ordinary `catch` clauses, which will bubble up to the calling context if uncaught in the same way exceptions bubble up in synchronous code.
{:.note} {:.note}
> Use `Amp\call()` to always return a promise instead of a `\Generator` from your public APIs. Generators are an implementation detail that shouldn't be leaked to API consumers. > Previous versions of Amp used Generators for a similar purpose, but fibers can be interrupted anywhere in the stack making previous helpers like `Amp\call()` unnecessary.
## Yield Behavior At any given time, only one fiber is running. When a coroutine suspends, execution of the coroutine is temporarily
interrupted, allowing other tasks to be run. Execution is resumed once a timer expires, stream operations are possible,
All `yield`s in a coroutine must be one of the following three types: or any awaited `Future` completes.
| Yieldable | Description |
| --------------| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Amp\Promise` | Any promise instance may be yielded and control will be returned to the coroutine once the promise resolves. If resolution fails the relevant exception is thrown into the generator and must be handled by the application or it will bubble up. If resolution succeeds the promise's resolved value is sent back into the generator. |
| `React\Promise\PromiseInterface` | Same as `Amp\Promise`. Any React promise will automatically be adapted to an Amp promise. |
| `array` | Yielding an array of promises combines them implicitly using `Amp\Promise\all()`. An array with elements not being promises will result in an `Amp\InvalidYieldError`. |
## Yield vs. Yield From
`yield` is used to "await" promises, `yield from` can be used to delegate to a sub-routine. `yield from` should only be used to delegate to private methods, any public API should always return promises instead of generators.
When a promise is yielded from within a `\Generator`, `\Generator` will be paused and continue as soon as the promise is resolved. Use `yield from` to yield another `\Generator`. Instead of using `yield from`, you can also use `yield new Coroutine($this->bar());` or `yield call([$this, "bar"]);`.
An example:
```php ```php
class Foo <?php
{
public function delegationWithCoroutine(): Amp\Promise
{
return new Amp\Coroutine($this->bar());
}
public function delegationWithYieldFrom(): Amp\Promise require __DIR__ . '/vendor/autoload.php';
{
return Amp\call(function () {
return yield from $this->bar();
});
}
public function delegationWithCallable(): Amp\Promise use Revolt\EventLoop;
{
return Amp\call([$this, 'bar']);
}
public function bar(): Generator $suspension = EventLoop::createSuspension();
{
yield new Amp\Success(1);
yield new Amp\Success(2);
return yield new Amp\Success(3);
}
}
Amp\Loop::run(function () { EventLoop::delay(5, function () use ($suspension): void {
$foo = new Foo(); print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;
$r1 = yield $foo->delegationWithCoroutine();
$r2 = yield $foo->delegationWithYieldFrom(); $suspension->resume(null);
$r3 = yield $foo->delegationWithCallable();
var_dump($r1);
var_dump($r2);
var_dump($r3);
}); });
print '++ Suspending to event loop...' . PHP_EOL;
$suspension->suspend();
print '++ Script end' . PHP_EOL;
``` ```
Outputs: Suspending and resuming fibers is handled by the `Suspension` API of [Revolt](https://revolt.run).
```
int(3)
int(3)
int(3)
```
For further information about `yield from`, consult the [PHP manual](http://php.net/manual/en/language.generators.syntax.php#control-structures.yield.from). {:.note}
> TODO: Note about futures on public APIs vs. awaiting them internally.

View File

@ -1,38 +0,0 @@
---
layout: docs
title: Coroutine Helpers
permalink: /coroutines/helpers
---
`Amp\Coroutine` requires an already instantiated `Generator` to be passed to its constructor. Always calling a callable before passing the `Generator` to `Amp\Coroutine` is unnecessary boilerplate.
## `coroutine()`
Returns a new function that wraps `$callback` in a promise/coroutine-aware function that automatically runs generators as coroutines. The returned function always returns a promise when invoked. Errors have to be handled by the callback caller or they will go unnoticed.
```php
function coroutine(callable $callback): callable { ... }
```
Use this function to create a coroutine-aware callable for a promise-aware callback caller.
## `asyncCoroutine()`
Same as `coroutine()` but doesn't return a `Promise` when the returned callback is called. Instead, promises are passed to `Amp\Promise\rethrow()` to handle errors automatically.
## `call()`
```php
function call(callable $callback, ...$args): Promise { ... }
```
Calls the given function, always returning a promise. If the function returns a `Generator`, it will be run as a coroutine. If the function throws, a failed promise will be returned.
`call($callable, ...$args)` is equivalent to `coroutine($callable)(...$args)`.
## `asyncCall()`
```php
function asyncCall(callable $callback, ...$args) { ... }
```
Same as `call()`, but doesn't return the `Promise`. Promises are automatically passed to `Amp\Promise\rethrow` for error handling.

View File

@ -1,5 +1,7 @@
--- ---
layout: docs title: Promises permalink: /promises/ layout: "docs"
title: "Futures"
permalink: "/promises/"
--- ---
A `Promise` is an object representing the eventual result of an asynchronous operation. There are three states: A `Promise` is an object representing the eventual result of an asynchronous operation. There are three states:

View File

@ -1,19 +1,18 @@
--- ---
layout: docs layout: "docs"
title: Promise Combinators title: "Promise Combinators"
permalink: /promises/combinators permalink: "/promises/combinators"
--- ---
Multiple promises can be combined into a single promise using different functions. Multiple promises can be combined into a single promise using different functions.
## `all()` ## `all()`
`Amp\Promise\all()` combines an array of promise objects into a single promise that will resolve `Amp\Promise\all()` combines an array of promise objects into a single promise that will resolve when all promises in
when all promises in the group resolve. If any one of the `Amp\Promise` instances fails the the group resolve. If any one of the `Amp\Promise` instances fails the combinator's `Promise` will fail. Otherwise the
combinator's `Promise` will fail. Otherwise the resulting `Promise` succeeds with an array matching resulting `Promise` succeeds with an array matching keys from the input array to their resolved values.
keys from the input array to their resolved values.
The `all()` combinator is extremely powerful because it allows us to concurrently execute many The `all()` combinator is extremely powerful because it allows us to concurrently execute many asynchronous operations
asynchronous operations at the same time. Let's look at a simple example using the Amp HTTP client at the same time. Let's look at a simple example using the Amp HTTP client
([Artax](https://github.com/amphp/artax)) to retrieve multiple HTTP resources concurrently: ([Artax](https://github.com/amphp/artax)) to retrieve multiple HTTP resources concurrently:
```php ```php
@ -60,16 +59,17 @@ Loop::run(function () {
## `some()` ## `some()`
`Amp\Promise\some()` is the same as `all()` except that it tolerates individual failures. As long `Amp\Promise\some()` is the same as `all()` except that it tolerates individual failures. As long as at least one
as at least one promise in the passed succeeds, the combined promise will succeed. The successful promise in the passed succeeds, the combined promise will succeed. The successful resolution value is an array of the
resolution value is an array of the form `[$arrayOfErrors, $arrayOfValues]`. The individual keys form `[$arrayOfErrors, $arrayOfValues]`. The individual keys in the component arrays are preserved from the promise
in the component arrays are preserved from the promise array passed to the functor for evaluation. array passed to the functor for evaluation.
## `any()` ## `any()`
`Amp\Promise\any()` is the same as `some()` except that it tolerates all failures. It will succeed even if all promises failed. `Amp\Promise\any()` is the same as `some()` except that it tolerates all failures. It will succeed even if all promises
failed.
## `first()` ## `first()`
`Amp\Promise\first()` resolves with the first successful result. The resulting promise will only fail if all `Amp\Promise\first()` resolves with the first successful result. The resulting promise will only fail if all promises in
promises in the group fail or if the promise array is empty. the group fail or if the promise array is empty.

View File

@ -0,0 +1,33 @@
---
layout: "docs"
title: "Promise Helpers"
permalink: "/promises/miscellaneous"
---
Amp offers some small promise helpers, namely
* `Amp\Promise\rethrow()`
* `Amp\Promise\timeout()`
* `Amp\Promise\wait()`
## `rethrow()`
`rethrow(Amp\Promise|React\Promise\PromiseInterface): void` subscribes to the passed `Promise` and forwards all errors
to the event loop. That handler can log these failures or the event loop will stop if no such handler exists.
`rethrow()` is useful whenever you want to fire and forget, but still care about any errors that happen.
## `timeout()`
`timeout(Amp\Promise|React\Promise\PromiseInterface, int $timeout): Amp\Promise` applies a timeout to the passed
promise, either resolving with the original value or error reason in case the promise resolves within the timeout
period, or otherwise fails the returned promise with an `Amp\TimeoutException`.
Note that `timeout()` does not cancel any operation or frees any resources. If available, use dedicated API options
instead, e.g. for socket connect timeouts.
## `wait()`
`wait(Amp\Promise|React\Promise\PromiseInterface): mixed` can be used to synchronously wait for a promise to resolve. It
returns the result value or throws an exception in case of an error. `wait()` blocks and calls `Loop::run()` internally.
It SHOULD NOT be used in fully asynchronous applications, but only when integrating async APIs into an otherwise
synchronous application.

View File

@ -1,27 +1,42 @@
--- ---
layout: docs layout: "docs"
title: Introduction title: "Introduction"
permalink: / permalink: "/"
--- ---
Amp is a non-blocking concurrency framework for PHP. It provides an event loop, promises and streams as a base for asynchronous programming. Amp is a set of seamlessly integrated concurrency libraries for PHP based on [Revolt](https://revolt.run/). This package
provides futures and cancellations as a base for asynchronous programming.
Promises in combination with generators are used to build coroutines, which allow writing asynchronous code just like synchronous code, without any callbacks. Amp makes heavy use of fibers shipped with PHP 8.1 to write asynchronous code just like synchronous, blocking code. In
contrast to earlier versions, there's no need for generator based coroutines or callbacks. Similar to threads, each
fiber has its own call stack, but fibers are scheduled cooperatively by the event loop. Use `Amp\async()` to run things
concurrently.
## Installation ## Installation
``` This package can be installed as a [Composer](https://getcomposer.org/) dependency.
```bash
composer require amphp/amp composer require amphp/amp
``` ```
If you use this library, it's very likely you want to schedule events using [Revolt's event loop](https://revolt.run),
which you should require separately, even if it's automatically installed as a dependency.
```bash
composer require revolt/event-loop
```
## Preamble ## Preamble
The weak link when managing concurrency is humans; we simply don't think asynchronously or in parallel. Instead, we're really good at doing one thing at a time and the world around us generally fits this model. So to effectively design for concurrent processing in our code we have a couple of options: The weak link when managing concurrency is humans; we simply don't think asynchronously or in parallel. Instead, we're
very good at doing one thing at a time and the world around us generally fits this model. So to effectively design for
concurrent processing in our code we have a couple of options:
1. Get smarter (not feasible); 1. Get smarter (not feasible);
2. Abstract concurrent task execution to make it feel synchronous. 2. Abstract concurrent task execution to make it feel synchronous.
## Contents ## Contents
Amp provides an [event loop](./event-loop/README.md), [promises](./promises/README.md) and [asynchronous iterators](./iterators/README.md) as building blocks for (fully) asynchronous libraries and applications. [Coroutines](./coroutines/README.md) make asynchronous code feel as synchronous code. Amp provides [futures](./futures/README.md) and [cancellations](./cancellation/README.md) as building blocks for
(partially and fully) asynchronous libraries and applications. [Coroutines](./coroutines/README.md) make asynchronous
Start with the [Introduction to Event Loops](./event-loop/). code feel as synchronous code.

View File

@ -1,25 +0,0 @@
---
layout: docs
title: Promise Helpers
permalink: /promises/miscellaneous
---
Amp offers some small promise helpers, namely
* `Amp\Promise\rethrow()`
* `Amp\Promise\timeout()`
* `Amp\Promise\wait()`
## `rethrow()`
`rethrow(Amp\Promise|React\Promise\PromiseInterface): void` subscribes to the passed `Promise` and forwards all errors to the event loop. That handler can log these failures or the event loop will stop if no such handler exists.
`rethrow()` is useful whenever you want to fire and forget, but still care about any errors that happen.
## `timeout()`
`timeout(Amp\Promise|React\Promise\PromiseInterface, int $timeout): Amp\Promise` applies a timeout to the passed promise, either resolving with the original value or error reason in case the promise resolves within the timeout period, or otherwise fails the returned promise with an `Amp\TimeoutException`.
Note that `timeout()` does not cancel any operation or frees any resources. If available, use dedicated API options instead, e.g. for socket connect timeouts.
## `wait()`
`wait(Amp\Promise|React\Promise\PromiseInterface): mixed` can be used to synchronously wait for a promise to resolve. It returns the result value or throws an exception in case of an error. `wait()` blocks and calls `Loop::run()` internally. It SHOULD NOT be used in fully asynchronous applications, but only when integrating async APIs into an otherwise synchronous application.