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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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.
|
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
|
||||
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.
|
||||
|
@ -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