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.

View File

@ -1,102 +1,40 @@
---
layout: docs
title: Coroutines
permalink: /coroutines/
layout: "docs"
title: "Coroutines"
permalink: "/coroutines/"
---
Coroutines are interruptible functions. In PHP they can be implemented using [generators](http://php.net/manual/en/language.generators.overview.php).
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.
Coroutines are interruptible functions. In PHP they can be implemented using [Fibers](https://wiki.php.net/rfc/fibers).
{:.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
All `yield`s in a coroutine must be one of the following three types:
| 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:
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,
or any awaited `Future` completes.
```php
class Foo
{
public function delegationWithCoroutine(): Amp\Promise
{
return new Amp\Coroutine($this->bar());
}
<?php
public function delegationWithYieldFrom(): Amp\Promise
{
return Amp\call(function () {
return yield from $this->bar();
});
}
require __DIR__ . '/vendor/autoload.php';
public function delegationWithCallable(): Amp\Promise
{
return Amp\call([$this, 'bar']);
}
use Revolt\EventLoop;
public function bar(): Generator
{
yield new Amp\Success(1);
yield new Amp\Success(2);
return yield new Amp\Success(3);
}
}
$suspension = EventLoop::createSuspension();
Amp\Loop::run(function () {
$foo = new Foo();
$r1 = yield $foo->delegationWithCoroutine();
$r2 = yield $foo->delegationWithYieldFrom();
$r3 = yield $foo->delegationWithCallable();
var_dump($r1);
var_dump($r2);
var_dump($r3);
EventLoop::delay(5, function () use ($suspension): void {
print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;
$suspension->resume(null);
});
print '++ Suspending to event loop...' . PHP_EOL;
$suspension->suspend();
print '++ Script end' . PHP_EOL;
```
Outputs:
```
int(3)
int(3)
int(3)
```
Suspending and resuming fibers is handled by the `Suspension` API of [Revolt](https://revolt.run).
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:

View File

@ -1,19 +1,18 @@
---
layout: docs
title: Promise Combinators
permalink: /promises/combinators
layout: "docs"
title: "Promise Combinators"
permalink: "/promises/combinators"
---
Multiple promises can be combined into a single promise using different functions.
## `all()`
`Amp\Promise\all()` combines an array of promise objects into a single promise that will resolve
when all promises in the group resolve. If any one of the `Amp\Promise` instances fails the
combinator's `Promise` will fail. Otherwise the resulting `Promise` succeeds with an array matching
keys from the input array to their resolved values.
`Amp\Promise\all()` combines an array of promise objects into a single promise that will resolve when all promises in
the group resolve. If any one of the `Amp\Promise` instances fails the combinator's `Promise` will fail. Otherwise the
resulting `Promise` succeeds with an array matching keys from the input array to their resolved values.
The `all()` combinator is extremely powerful because it allows us to concurrently execute many
asynchronous operations at the same time. Let's look at a simple example using the Amp HTTP client
The `all()` combinator is extremely powerful because it allows us to concurrently execute many asynchronous operations
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:
```php
@ -60,16 +59,17 @@ Loop::run(function () {
## `some()`
`Amp\Promise\some()` is the same as `all()` except that it tolerates individual failures. As long
as at least one promise in the passed succeeds, the combined promise will succeed. The successful
resolution value is an array of the form `[$arrayOfErrors, $arrayOfValues]`. The individual keys
in the component arrays are preserved from the promise array passed to the functor for evaluation.
`Amp\Promise\some()` is the same as `all()` except that it tolerates individual failures. As long as at least one
promise in the passed succeeds, the combined promise will succeed. The successful resolution value is an array of the
form `[$arrayOfErrors, $arrayOfValues]`. The individual keys in the component arrays are preserved from the promise
array passed to the functor for evaluation.
## `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()`
`Amp\Promise\first()` resolves with the first successful result. The resulting promise will only fail if all
promises in the group fail or if the promise array is empty.
`Amp\Promise\first()` resolves with the first successful result. The resulting promise will only fail if all promises in
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
title: Introduction
permalink: /
layout: "docs"
title: "Introduction"
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
```
This package can be installed as a [Composer](https://getcomposer.org/) dependency.
```bash
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
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);
2. Abstract concurrent task execution to make it feel synchronous.
## 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.
Start with the [Introduction to Event Loops](./event-loop/).
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
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.