|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- # Guzzle Promises
-
- [Promises/A+](https://promisesaplus.com/) implementation that handles promise
- chaining and resolution iteratively, allowing for "infinite" promise chaining
- while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
- for a general introduction to promises.
-
- - [Features](#features)
- - [Quick start](#quick-start)
- - [Synchronous wait](#synchronous-wait)
- - [Cancellation](#cancellation)
- - [API](#api)
- - [Promise](#promise)
- - [FulfilledPromise](#fulfilledpromise)
- - [RejectedPromise](#rejectedpromise)
- - [Promise interop](#promise-interop)
- - [Implementation notes](#implementation-notes)
-
-
- # Features
-
- - [Promises/A+](https://promisesaplus.com/) implementation.
- - Promise resolution and chaining is handled iteratively, allowing for
- "infinite" promise chaining.
- - Promises have a synchronous `wait` method.
- - Promises can be cancelled.
- - Works with any object that has a `then` function.
- - C# style async/await coroutine promises using
- `GuzzleHttp\Promise\coroutine()`.
-
-
- # Quick start
-
- A *promise* represents the eventual result of an asynchronous operation. The
- primary way of interacting with a promise is through its `then` method, which
- registers callbacks to receive either a promise's eventual value or the reason
- why the promise cannot be fulfilled.
-
-
- ## Callbacks
-
- Callbacks are registered with the `then` method by providing an optional
- `$onFulfilled` followed by an optional `$onRejected` function.
-
-
- ```php
- use GuzzleHttp\Promise\Promise;
-
- $promise = new Promise();
- $promise->then(
- // $onFulfilled
- function ($value) {
- echo 'The promise was fulfilled.';
- },
- // $onRejected
- function ($reason) {
- echo 'The promise was rejected.';
- }
- );
- ```
-
- *Resolving* a promise means that you either fulfill a promise with a *value* or
- reject a promise with a *reason*. Resolving a promises triggers callbacks
- registered with the promises's `then` method. These callbacks are triggered
- only once and in the order in which they were added.
-
-
- ## Resolving a promise
-
- Promises are fulfilled using the `resolve($value)` method. Resolving a promise
- with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
- all of the onFulfilled callbacks (resolving a promise with a rejected promise
- will reject the promise and trigger the `$onRejected` callbacks).
-
- ```php
- use GuzzleHttp\Promise\Promise;
-
- $promise = new Promise();
- $promise
- ->then(function ($value) {
- // Return a value and don't break the chain
- return "Hello, " . $value;
- })
- // This then is executed after the first then and receives the value
- // returned from the first then.
- ->then(function ($value) {
- echo $value;
- });
-
- // Resolving the promise triggers the $onFulfilled callbacks and outputs
- // "Hello, reader".
- $promise->resolve('reader.');
- ```
-
-
- ## Promise forwarding
-
- Promises can be chained one after the other. Each then in the chain is a new
- promise. The return value of a promise is what's forwarded to the next
- promise in the chain. Returning a promise in a `then` callback will cause the
- subsequent promises in the chain to only be fulfilled when the returned promise
- has been fulfilled. The next promise in the chain will be invoked with the
- resolved value of the promise.
-
- ```php
- use GuzzleHttp\Promise\Promise;
-
- $promise = new Promise();
- $nextPromise = new Promise();
-
- $promise
- ->then(function ($value) use ($nextPromise) {
- echo $value;
- return $nextPromise;
- })
- ->then(function ($value) {
- echo $value;
- });
-
- // Triggers the first callback and outputs "A"
- $promise->resolve('A');
- // Triggers the second callback and outputs "B"
- $nextPromise->resolve('B');
- ```
-
- ## Promise rejection
-
- When a promise is rejected, the `$onRejected` callbacks are invoked with the
- rejection reason.
-
- ```php
- use GuzzleHttp\Promise\Promise;
-
- $promise = new Promise();
- $promise->then(null, function ($reason) {
- echo $reason;
- });
-
- $promise->reject('Error!');
- // Outputs "Error!"
- ```
-
- ## Rejection forwarding
-
- If an exception is thrown in an `$onRejected` callback, subsequent
- `$onRejected` callbacks are invoked with the thrown exception as the reason.
-
- ```php
- use GuzzleHttp\Promise\Promise;
-
- $promise = new Promise();
- $promise->then(null, function ($reason) {
- throw new \Exception($reason);
- })->then(null, function ($reason) {
- assert($reason->getMessage() === 'Error!');
- });
-
- $promise->reject('Error!');
- ```
-
- You can also forward a rejection down the promise chain by returning a
- `GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
- `$onRejected` callback.
-
- ```php
- use GuzzleHttp\Promise\Promise;
- use GuzzleHttp\Promise\RejectedPromise;
-
- $promise = new Promise();
- $promise->then(null, function ($reason) {
- return new RejectedPromise($reason);
- })->then(null, function ($reason) {
- assert($reason === 'Error!');
- });
-
- $promise->reject('Error!');
- ```
-
- If an exception is not thrown in a `$onRejected` callback and the callback
- does not return a rejected promise, downstream `$onFulfilled` callbacks are
- invoked using the value returned from the `$onRejected` callback.
-
- ```php
- use GuzzleHttp\Promise\Promise;
- use GuzzleHttp\Promise\RejectedPromise;
-
- $promise = new Promise();
- $promise
- ->then(null, function ($reason) {
- return "It's ok";
- })
- ->then(function ($value) {
- assert($value === "It's ok");
- });
-
- $promise->reject('Error!');
- ```
-
- # Synchronous wait
-
- You can synchronously force promises to complete using a promise's `wait`
- method. When creating a promise, you can provide a wait function that is used
- to synchronously force a promise to complete. When a wait function is invoked
- it is expected to deliver a value to the promise or reject the promise. If the
- wait function does not deliver a value, then an exception is thrown. The wait
- function provided to a promise constructor is invoked when the `wait` function
- of the promise is called.
-
- ```php
- $promise = new Promise(function () use (&$promise) {
- $promise->resolve('foo');
- });
-
- // Calling wait will return the value of the promise.
- echo $promise->wait(); // outputs "foo"
- ```
-
- If an exception is encountered while invoking the wait function of a promise,
- the promise is rejected with the exception and the exception is thrown.
-
- ```php
- $promise = new Promise(function () use (&$promise) {
- throw new \Exception('foo');
- });
-
- $promise->wait(); // throws the exception.
- ```
-
- Calling `wait` on a promise that has been fulfilled will not trigger the wait
- function. It will simply return the previously resolved value.
-
- ```php
- $promise = new Promise(function () { die('this is not called!'); });
- $promise->resolve('foo');
- echo $promise->wait(); // outputs "foo"
- ```
-
- Calling `wait` on a promise that has been rejected will throw an exception. If
- the rejection reason is an instance of `\Exception` the reason is thrown.
- Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
- can be obtained by calling the `getReason` method of the exception.
-
- ```php
- $promise = new Promise();
- $promise->reject('foo');
- $promise->wait();
- ```
-
- > PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
-
-
- ## Unwrapping a promise
-
- When synchronously waiting on a promise, you are joining the state of the
- promise into the current state of execution (i.e., return the value of the
- promise if it was fulfilled or throw an exception if it was rejected). This is
- called "unwrapping" the promise. Waiting on a promise will by default unwrap
- the promise state.
-
- You can force a promise to resolve and *not* unwrap the state of the promise
- by passing `false` to the first argument of the `wait` function:
-
- ```php
- $promise = new Promise();
- $promise->reject('foo');
- // This will not throw an exception. It simply ensures the promise has
- // been resolved.
- $promise->wait(false);
- ```
-
- When unwrapping a promise, the resolved value of the promise will be waited
- upon until the unwrapped value is not a promise. This means that if you resolve
- promise A with a promise B and unwrap promise A, the value returned by the
- wait function will be the value delivered to promise B.
-
- **Note**: when you do not unwrap the promise, no value is returned.
-
-
- # Cancellation
-
- You can cancel a promise that has not yet been fulfilled using the `cancel()`
- method of a promise. When creating a promise you can provide an optional
- cancel function that when invoked cancels the action of computing a resolution
- of the promise.
-
-
- # API
-
-
- ## Promise
-
- When creating a promise object, you can provide an optional `$waitFn` and
- `$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
- expected to resolve the promise. `$cancelFn` is a function with no arguments
- that is expected to cancel the computation of a promise. It is invoked when the
- `cancel()` method of a promise is called.
-
- ```php
- use GuzzleHttp\Promise\Promise;
-
- $promise = new Promise(
- function () use (&$promise) {
- $promise->resolve('waited');
- },
- function () {
- // do something that will cancel the promise computation (e.g., close
- // a socket, cancel a database query, etc...)
- }
- );
-
- assert('waited' === $promise->wait());
- ```
-
- A promise has the following methods:
-
- - `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
-
- Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
-
- - `otherwise(callable $onRejected) : PromiseInterface`
-
- Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
-
- - `wait($unwrap = true) : mixed`
-
- Synchronously waits on the promise to complete.
-
- `$unwrap` controls whether or not the value of the promise is returned for a
- fulfilled promise or if an exception is thrown if the promise is rejected.
- This is set to `true` by default.
-
- - `cancel()`
-
- Attempts to cancel the promise if possible. The promise being cancelled and
- the parent most ancestor that has not yet been resolved will also be
- cancelled. Any promises waiting on the cancelled promise to resolve will also
- be cancelled.
-
- - `getState() : string`
-
- Returns the state of the promise. One of `pending`, `fulfilled`, or
- `rejected`.
-
- - `resolve($value)`
-
- Fulfills the promise with the given `$value`.
-
- - `reject($reason)`
-
- Rejects the promise with the given `$reason`.
-
-
- ## FulfilledPromise
-
- A fulfilled promise can be created to represent a promise that has been
- fulfilled.
-
- ```php
- use GuzzleHttp\Promise\FulfilledPromise;
-
- $promise = new FulfilledPromise('value');
-
- // Fulfilled callbacks are immediately invoked.
- $promise->then(function ($value) {
- echo $value;
- });
- ```
-
-
- ## RejectedPromise
-
- A rejected promise can be created to represent a promise that has been
- rejected.
-
- ```php
- use GuzzleHttp\Promise\RejectedPromise;
-
- $promise = new RejectedPromise('Error');
-
- // Rejected callbacks are immediately invoked.
- $promise->then(null, function ($reason) {
- echo $reason;
- });
- ```
-
-
- # Promise interop
-
- This library works with foreign promises that have a `then` method. This means
- you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
- for example. When a foreign promise is returned inside of a then method
- callback, promise resolution will occur recursively.
-
- ```php
- // Create a React promise
- $deferred = new React\Promise\Deferred();
- $reactPromise = $deferred->promise();
-
- // Create a Guzzle promise that is fulfilled with a React promise.
- $guzzlePromise = new \GuzzleHttp\Promise\Promise();
- $guzzlePromise->then(function ($value) use ($reactPromise) {
- // Do something something with the value...
- // Return the React promise
- return $reactPromise;
- });
- ```
-
- Please note that wait and cancel chaining is no longer possible when forwarding
- a foreign promise. You will need to wrap a third-party promise with a Guzzle
- promise in order to utilize wait and cancel functions with foreign promises.
-
-
- ## Event Loop Integration
-
- In order to keep the stack size constant, Guzzle promises are resolved
- asynchronously using a task queue. When waiting on promises synchronously, the
- task queue will be automatically run to ensure that the blocking promise and
- any forwarded promises are resolved. When using promises asynchronously in an
- event loop, you will need to run the task queue on each tick of the loop. If
- you do not run the task queue, then promises will not be resolved.
-
- You can run the task queue using the `run()` method of the global task queue
- instance.
-
- ```php
- // Get the global task queue
- $queue = \GuzzleHttp\Promise\queue();
- $queue->run();
- ```
-
- For example, you could use Guzzle promises with React using a periodic timer:
-
- ```php
- $loop = React\EventLoop\Factory::create();
- $loop->addPeriodicTimer(0, [$queue, 'run']);
- ```
-
- *TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
-
-
- # Implementation notes
-
-
- ## Promise resolution and chaining is handled iteratively
-
- By shuffling pending handlers from one owner to another, promises are
- resolved iteratively, allowing for "infinite" then chaining.
-
- ```php
- <?php
- require 'vendor/autoload.php';
-
- use GuzzleHttp\Promise\Promise;
-
- $parent = new Promise();
- $p = $parent;
-
- for ($i = 0; $i < 1000; $i++) {
- $p = $p->then(function ($v) {
- // The stack size remains constant (a good thing)
- echo xdebug_get_stack_depth() . ', ';
- return $v + 1;
- });
- }
-
- $parent->resolve(0);
- var_dump($p->wait()); // int(1000)
-
- ```
-
- When a promise is fulfilled or rejected with a non-promise value, the promise
- then takes ownership of the handlers of each child promise and delivers values
- down the chain without using recursion.
-
- When a promise is resolved with another promise, the original promise transfers
- all of its pending handlers to the new promise. When the new promise is
- eventually resolved, all of the pending handlers are delivered the forwarded
- value.
-
-
- ## A promise is the deferred.
-
- Some promise libraries implement promises using a deferred object to represent
- a computation and a promise object to represent the delivery of the result of
- the computation. This is a nice separation of computation and delivery because
- consumers of the promise cannot modify the value that will be eventually
- delivered.
-
- One side effect of being able to implement promise resolution and chaining
- iteratively is that you need to be able for one promise to reach into the state
- of another promise to shuffle around ownership of handlers. In order to achieve
- this without making the handlers of a promise publicly mutable, a promise is
- also the deferred value, allowing promises of the same parent class to reach
- into and modify the private properties of promises of the same type. While this
- does allow consumers of the value to modify the resolution or rejection of the
- deferred, it is a small price to pay for keeping the stack size constant.
-
- ```php
- $promise = new Promise();
- $promise->then(function ($value) { echo $value; });
- // The promise is the deferred value, so you can deliver a value to it.
- $promise->resolve('foo');
- // prints "foo"
- ```
|