mirror of
https://github.com/danog/amp.git
synced 2024-12-11 17:09:40 +01:00
Update documentation
This commit is contained in:
parent
1a2be8f2b2
commit
d70896df73
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
|
@ -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:
|
||||||
|
|
@ -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.
|
33
docs/futures/miscellaneous.md
Normal file
33
docs/futures/miscellaneous.md
Normal 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.
|
@ -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.
|
||||||
|
@ -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.
|
|
Loading…
Reference in New Issue
Block a user