Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
# Guzzle Promises[Promises/A+](https://promisesaplus.com/) implementation that handles promisechaining and resolution iteratively, allowing for "infinite" promise chainingwhile 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::of()`.## Quick StartA *promise* represents the eventual result of an asynchronous operation. Theprimary way of interacting with a promise is through its `then` method, whichregisters callbacks to receive either a promise's eventual value or the reasonwhy the promise cannot be fulfilled.### CallbacksCallbacks are registered with the `then` method by providing an optional`$onFulfilled` followed by an optional `$onRejected` function.```phpuse GuzzleHttp\Promise\Promise;$promise = new Promise();$promise->then(// $onFulfilledfunction ($value) {echo 'The promise was fulfilled.';},// $onRejectedfunction ($reason) {echo 'The promise was rejected.';});```*Resolving* a promise means that you either fulfill a promise with a *value* orreject a promise with a *reason*. Resolving a promise triggers callbacksregistered with the promise's `then` method. These callbacks are triggeredonly once and in the order in which they were added.### Resolving a PromisePromises are fulfilled using the `resolve($value)` method. Resolving a promisewith any value other than a `GuzzleHttp\Promise\RejectedPromise` will triggerall of the onFulfilled callbacks (resolving a promise with a rejected promisewill reject the promise and trigger the `$onRejected` callbacks).```phpuse GuzzleHttp\Promise\Promise;$promise = new Promise();$promise->then(function ($value) {// Return a value and don't break the chainreturn "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 ForwardingPromises can be chained one after the other. Each then in the chain is a newpromise. The return value of a promise is what's forwarded to the nextpromise in the chain. Returning a promise in a `then` callback will cause thesubsequent promises in the chain to only be fulfilled when the returned promisehas been fulfilled. The next promise in the chain will be invoked with theresolved value of the promise.```phpuse 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 RejectionWhen a promise is rejected, the `$onRejected` callbacks are invoked with therejection reason.```phpuse GuzzleHttp\Promise\Promise;$promise = new Promise();$promise->then(null, function ($reason) {echo $reason;});$promise->reject('Error!');// Outputs "Error!"```### Rejection ForwardingIf an exception is thrown in an `$onRejected` callback, subsequent`$onRejected` callbacks are invoked with the thrown exception as the reason.```phpuse 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.```phpuse 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 callbackdoes not return a rejected promise, downstream `$onFulfilled` callbacks areinvoked using the value returned from the `$onRejected` callback.```phpuse GuzzleHttp\Promise\Promise;$promise = new Promise();$promise->then(null, function ($reason) {return "It's ok";})->then(function ($value) {assert($value === "It's ok");});$promise->reject('Error!');```## Synchronous WaitYou can synchronously force promises to complete using a promise's `wait`method. When creating a promise, you can provide a wait function that is usedto synchronously force a promise to complete. When a wait function is invokedit is expected to deliver a value to the promise or reject the promise. If thewait function does not deliver a value, then an exception is thrown. The waitfunction provided to a promise constructor is invoked when the `wait` functionof 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 waitfunction. 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. Ifthe rejection reason is an instance of `\Exception` the reason is thrown.Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reasoncan 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 PromiseWhen synchronously waiting on a promise, you are joining the state of thepromise into the current state of execution (i.e., return the value of thepromise if it was fulfilled or throw an exception if it was rejected). This iscalled "unwrapping" the promise. Waiting on a promise will by default unwrapthe promise state.You can force a promise to resolve and *not* unwrap the state of the promiseby 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 waitedupon until the unwrapped value is not a promise. This means that if you resolvepromise A with a promise B and unwrap promise A, the value returned by thewait function will be the value delivered to promise B.**Note**: when you do not unwrap the promise, no value is returned.## CancellationYou 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 optionalcancel function that when invoked cancels the action of computing a resolutionof the promise.## API### PromiseWhen creating a promise object, you can provide an optional `$waitFn` and`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and isexpected to resolve the promise. `$cancelFn` is a function with no argumentsthat is expected to cancel the computation of a promise. It is invoked when the`cancel()` method of a promise is called.```phpuse 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 afulfilled 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 andthe parent most ancestor that has not yet been resolved will also becancelled. Any promises waiting on the cancelled promise to resolve will alsobe 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`.### FulfilledPromiseA fulfilled promise can be created to represent a promise that has beenfulfilled.```phpuse GuzzleHttp\Promise\FulfilledPromise;$promise = new FulfilledPromise('value');// Fulfilled callbacks are immediately invoked.$promise->then(function ($value) {echo $value;});```### RejectedPromiseA rejected promise can be created to represent a promise that has beenrejected.```phpuse GuzzleHttp\Promise\RejectedPromise;$promise = new RejectedPromise('Error');// Rejected callbacks are immediately invoked.$promise->then(null, function ($reason) {echo $reason;});```## Promise InteroperabilityThis library works with foreign promises that have a `then` method. This meansyou can use Guzzle promises with [React promises](https://github.com/reactphp/promise)for example. When a foreign promise is returned inside of a then methodcallback, 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 promisereturn $reactPromise;});```Please note that wait and cancel chaining is no longer possible when forwardinga foreign promise. You will need to wrap a third-party promise with a Guzzlepromise in order to utilize wait and cancel functions with foreign promises.### Event Loop IntegrationIn order to keep the stack size constant, Guzzle promises are resolvedasynchronously using a task queue. When waiting on promises synchronously, thetask queue will be automatically run to ensure that the blocking promise andany forwarded promises are resolved. When using promises asynchronously in anevent loop, you will need to run the task queue on each tick of the loop. Ifyou 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 queueinstance.```php// Get the global task queue$queue = GuzzleHttp\Promise\Utils::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 IterativelyBy shuffling pending handlers from one owner to another, promises areresolved iteratively, allowing for "infinite" then chaining.```php<?phprequire '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 promisethen takes ownership of the handlers of each child promise and delivers valuesdown the chain without using recursion.When a promise is resolved with another promise, the original promise transfersall of its pending handlers to the new promise. When the new promise iseventually resolved, all of the pending handlers are delivered the forwardedvalue.### A Promise is the DeferredSome promise libraries implement promises using a deferred object to representa computation and a promise object to represent the delivery of the result ofthe computation. This is a nice separation of computation and delivery becauseconsumers of the promise cannot modify the value that will be eventuallydelivered.One side effect of being able to implement promise resolution and chainingiteratively is that you need to be able for one promise to reach into the stateof another promise to shuffle around ownership of handlers. In order to achievethis without making the handlers of a promise publicly mutable, a promise isalso the deferred value, allowing promises of the same parent class to reachinto and modify the private properties of promises of the same type. While thisdoes allow consumers of the value to modify the resolution or rejection of thedeferred, 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"```## Upgrading from Function APIA static API was first introduced in 1.4.0, in order to mitigate problems withfunctions conflicting between global and local copies of the package. Thefunction API will be removed in 2.0.0. A migration table has been provided herefor your convenience:| Original Function | Replacement Method ||----------------|----------------|| `queue` | `Utils::queue` || `task` | `Utils::task` || `promise_for` | `Create::promiseFor` || `rejection_for` | `Create::rejectionFor` || `exception_for` | `Create::exceptionFor` || `iter_for` | `Create::iterFor` || `inspect` | `Utils::inspect` || `inspect_all` | `Utils::inspectAll` || `unwrap` | `Utils::unwrap` || `all` | `Utils::all` || `some` | `Utils::some` || `any` | `Utils::any` || `settle` | `Utils::settle` || `each` | `Each::of` || `each_limit` | `Each::ofLimit` || `each_limit_all` | `Each::ofLimitAll` || `!is_fulfilled` | `Is::pending` || `is_fulfilled` | `Is::fulfilled` || `is_rejected` | `Is::rejected` || `is_settled` | `Is::settled` || `coroutine` | `Coroutine::of` |## SecurityIf you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information.## LicenseGuzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.## For EnterpriseAvailable as part of the Tidelift SubscriptionThe maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)