Dep: update

주로 PHAN
This commit is contained in:
2021-08-06 22:39:09 +09:00
parent 5310c2e7f6
commit b306601c72
1098 changed files with 92137 additions and 33228 deletions
+19
View File
@@ -1,5 +1,24 @@
# CHANGELOG
## 1.4.1 - 2021-02-18
- Fixed `each_limit` skipping promises and failing
## 1.4.0 - 2020-09-30
### Added
- Support for PHP 8
- Optional `$recursive` flag to `all`
- Replaced functions by static methods
### Fixed
- Fix empty `each` processing
- Fix promise handling for Iterators of non-unique keys
- Fixed `method_exists` crashes on PHP 8
- Memory leak on exceptions
## 1.3.1 - 2016-12-20
+35 -7
View File
@@ -26,7 +26,7 @@ for a general introduction to promises.
- Promises can be cancelled.
- Works with any object that has a `then` function.
- C# style async/await coroutine promises using
`GuzzleHttp\Promise\coroutine()`.
`GuzzleHttp\Promise\Coroutine::of()`.
# Quick start
@@ -88,7 +88,7 @@ $promise
});
// Resolving the promise triggers the $onFulfilled callbacks and outputs
// "Hello, reader".
// "Hello, reader."
$promise->resolve('reader.');
```
@@ -150,7 +150,7 @@ use GuzzleHttp\Promise\Promise;
$promise = new Promise();
$promise->then(null, function ($reason) {
throw new \Exception($reason);
throw new Exception($reason);
})->then(null, function ($reason) {
assert($reason->getMessage() === 'Error!');
});
@@ -182,7 +182,6 @@ invoked using the value returned from the `$onRejected` callback.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
$promise = new Promise();
$promise
@@ -220,7 +219,7 @@ the promise is rejected with the exception and the exception is thrown.
```php
$promise = new Promise(function () use (&$promise) {
throw new \Exception('foo');
throw new Exception('foo');
});
$promise->wait(); // throws the exception.
@@ -397,7 +396,7 @@ $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 = new GuzzleHttp\Promise\Promise();
$guzzlePromise->then(function ($value) use ($reactPromise) {
// Do something something with the value...
// Return the React promise
@@ -424,7 +423,7 @@ instance.
```php
// Get the global task queue
$queue = \GuzzleHttp\Promise\queue();
$queue = GuzzleHttp\Promise\Utils::queue();
$queue->run();
```
@@ -502,3 +501,32 @@ $promise->then(function ($value) { echo $value; });
$promise->resolve('foo');
// prints "foo"
```
## Upgrading from Function API
A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for 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` |
+9 -4
View File
@@ -11,10 +11,10 @@
}
],
"require": {
"php": ">=5.5.0"
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
"symfony/phpunit-bridge": "^4.4 || ^5.1"
},
"autoload": {
"psr-4": {
@@ -22,9 +22,14 @@
},
"files": ["src/functions_include.php"]
},
"autoload-dev": {
"psr-4": {
"GuzzleHttp\\Promise\\Tests\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/phpunit",
"test-ci": "vendor/bin/phpunit --coverage-text"
"test": "vendor/bin/simple-phpunit",
"test-ci": "vendor/bin/simple-phpunit --coverage-text"
},
"extra": {
"branch-alias": {
+1
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
+23 -5
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
use Exception;
@@ -9,7 +10,7 @@ use Throwable;
* Creates a promise that is resolved using a generator that yields values or
* promises (somewhat similar to C#'s async keyword).
*
* When called, the coroutine function will start an instance of the generator
* When called, the Coroutine::of method will start an instance of the generator
* and returns a promise that is fulfilled with its final yielded value.
*
* Control is returned back to the generator when the yielded promise settles.
@@ -22,7 +23,7 @@ use Throwable;
* return new Promise\FulfilledPromise($value);
* }
*
* $promise = Promise\coroutine(function () {
* $promise = Promise\Coroutine::of(function () {
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
@@ -38,6 +39,7 @@ use Throwable;
* @param callable $generatorFn Generator function to wrap into a promise.
*
* @return Promise
*
* @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
*/
final class Coroutine implements PromiseInterface
@@ -65,7 +67,23 @@ final class Coroutine implements PromiseInterface
$this->currentPromise->wait();
}
});
$this->nextCoroutine($this->generator->current());
try {
$this->nextCoroutine($this->generator->current());
} catch (\Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
$this->result->reject($throwable);
}
}
/**
* Create a new coroutine.
*
* @return self
*/
public static function of(callable $generatorFn)
{
return new self($generatorFn);
}
public function then(
@@ -108,7 +126,7 @@ final class Coroutine implements PromiseInterface
private function nextCoroutine($yielded)
{
$this->currentPromise = promise_for($yielded)
$this->currentPromise = Create::promiseFor($yielded)
->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
}
@@ -139,7 +157,7 @@ final class Coroutine implements PromiseInterface
{
unset($this->currentPromise);
try {
$nextYield = $this->generator->throw(exception_for($reason));
$nextYield = $this->generator->throw(Create::exceptionFor($reason));
// The throw was caught, so keep iterating on the coroutine
$this->nextCoroutine($nextYield);
} catch (Exception $exception) {
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace GuzzleHttp\Promise;
final class Create
{
/**
* Creates a promise for a value if the value is not a promise.
*
* @param mixed $value Promise or value.
*
* @return PromiseInterface
*/
public static function promiseFor($value)
{
if ($value instanceof PromiseInterface) {
return $value;
}
// Return a Guzzle promise that shadows the given promise.
if (is_object($value) && method_exists($value, 'then')) {
$wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
$cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
$promise = new Promise($wfn, $cfn);
$value->then([$promise, 'resolve'], [$promise, 'reject']);
return $promise;
}
return new FulfilledPromise($value);
}
/**
* Creates a rejected promise for a reason if the reason is not a promise.
* If the provided reason is a promise, then it is returned as-is.
*
* @param mixed $reason Promise or reason.
*
* @return PromiseInterface
*/
public static function rejectionFor($reason)
{
if ($reason instanceof PromiseInterface) {
return $reason;
}
return new RejectedPromise($reason);
}
/**
* Create an exception for a rejected promise value.
*
* @param mixed $reason
*
* @return \Exception|\Throwable
*/
public static function exceptionFor($reason)
{
if ($reason instanceof \Exception || $reason instanceof \Throwable) {
return $reason;
}
return new RejectionException($reason);
}
/**
* Returns an iterator for the given value.
*
* @param mixed $value
*
* @return \Iterator
*/
public static function iterFor($value)
{
if ($value instanceof \Iterator) {
return $value;
}
if (is_array($value)) {
return new \ArrayIterator($value);
}
return new \ArrayIterator([$value]);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace GuzzleHttp\Promise;
final class Each
{
/**
* Given an iterator that yields promises or values, returns a promise that
* is fulfilled with a null value when the iterator has been consumed or
* the aggregate promise has been fulfilled or rejected.
*
* $onFulfilled is a function that accepts the fulfilled value, iterator
* index, and the aggregate promise. The callback can invoke any necessary
* side effects and choose to resolve or reject the aggregate if needed.
*
* $onRejected is a function that accepts the rejection reason, iterator
* index, and the aggregate promise. The callback can invoke any necessary
* side effects and choose to resolve or reject the aggregate if needed.
*
* @param mixed $iterable Iterator or array to iterate over.
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*/
public static function of(
$iterable,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected
]))->promise();
}
/**
* Like of, but only allows a certain number of outstanding promises at any
* given time.
*
* $concurrency may be an integer or a function that accepts the number of
* pending promises and returns a numeric concurrency limit value to allow
* for dynamic a concurrency size.
*
* @param mixed $iterable
* @param int|callable $concurrency
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*/
public static function ofLimit(
$iterable,
$concurrency,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected,
'concurrency' => $concurrency
]))->promise();
}
/**
* Like limit, but ensures that no promise in the given $iterable argument
* is rejected. If any promise is rejected, then the aggregate promise is
* rejected with the encountered rejection.
*
* @param mixed $iterable
* @param int|callable $concurrency
* @param callable $onFulfilled
*
* @return PromiseInterface
*/
public static function ofLimitAll(
$iterable,
$concurrency,
callable $onFulfilled = null
) {
return each_limit(
$iterable,
$concurrency,
$onFulfilled,
function ($reason, $idx, PromiseInterface $aggregate) {
$aggregate->reject($reason);
}
);
}
}
+48 -23
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -9,22 +10,24 @@ class EachPromise implements PromisorInterface
{
private $pending = [];
/** @var \Iterator */
private $nextPendingIndex = 0;
/** @var \Iterator|null */
private $iterable;
/** @var callable|int */
/** @var callable|int|null */
private $concurrency;
/** @var callable */
/** @var callable|null */
private $onFulfilled;
/** @var callable */
/** @var callable|null */
private $onRejected;
/** @var Promise */
/** @var Promise|null */
private $aggregate;
/** @var bool */
/** @var bool|null */
private $mutex;
/**
@@ -45,12 +48,12 @@ class EachPromise implements PromisorInterface
* allowed number of outstanding concurrently executing promises,
* creating a capped pool of promises. There is no limit by default.
*
* @param mixed $iterable Promises or values to iterate.
* @param array $config Configuration options
* @param mixed $iterable Promises or values to iterate.
* @param array $config Configuration options
*/
public function __construct($iterable, array $config = [])
{
$this->iterable = iter_for($iterable);
$this->iterable = Create::iterFor($iterable);
if (isset($config['concurrency'])) {
$this->concurrency = $config['concurrency'];
@@ -65,6 +68,7 @@ class EachPromise implements PromisorInterface
}
}
/** @psalm-suppress InvalidNullableReturnType */
public function promise()
{
if ($this->aggregate) {
@@ -73,14 +77,29 @@ class EachPromise implements PromisorInterface
try {
$this->createPromise();
/** @psalm-assert Promise $this->aggregate */
$this->iterable->rewind();
$this->refillPending();
if (!$this->checkIfFinished()) {
$this->refillPending();
}
} catch (\Throwable $e) {
/**
* @psalm-suppress NullReference
* @phpstan-ignore-next-line
*/
$this->aggregate->reject($e);
} catch (\Exception $e) {
/**
* @psalm-suppress NullReference
* @phpstan-ignore-next-line
*/
$this->aggregate->reject($e);
}
/**
* @psalm-suppress NullableReturnStatement
* @phpstan-ignore-next-line
*/
return $this->aggregate;
}
@@ -89,17 +108,12 @@ class EachPromise implements PromisorInterface
$this->mutex = false;
$this->aggregate = new Promise(function () {
reset($this->pending);
if (empty($this->pending) && !$this->iterable->valid()) {
$this->aggregate->resolve(null);
return;
}
// Consume a potentially fluctuating list of promises while
// ensuring that indexes are maintained (precluding array_shift).
while ($promise = current($this->pending)) {
next($this->pending);
$promise->wait();
if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
if (Is::settled($this->aggregate)) {
return;
}
}
@@ -109,6 +123,7 @@ class EachPromise implements PromisorInterface
$clearFn = function () {
$this->iterable = $this->concurrency = $this->pending = null;
$this->onFulfilled = $this->onRejected = null;
$this->nextPendingIndex = 0;
};
$this->aggregate->then($clearFn, $clearFn);
@@ -148,22 +163,32 @@ class EachPromise implements PromisorInterface
return false;
}
$promise = promise_for($this->iterable->current());
$idx = $this->iterable->key();
$promise = Create::promiseFor($this->iterable->current());
$key = $this->iterable->key();
// Iterable keys may not be unique, so we use a counter to
// guarantee uniqueness
$idx = $this->nextPendingIndex++;
$this->pending[$idx] = $promise->then(
function ($value) use ($idx) {
function ($value) use ($idx, $key) {
if ($this->onFulfilled) {
call_user_func(
$this->onFulfilled, $value, $idx, $this->aggregate
$this->onFulfilled,
$value,
$key,
$this->aggregate
);
}
$this->step($idx);
},
function ($reason) use ($idx) {
function ($reason) use ($idx, $key) {
if ($this->onRejected) {
call_user_func(
$this->onRejected, $reason, $idx, $this->aggregate
$this->onRejected,
$reason,
$key,
$this->aggregate
);
}
$this->step($idx);
@@ -201,7 +226,7 @@ class EachPromise implements PromisorInterface
private function step($idx)
{
// If the promise was already resolved, then ignore this step.
if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
if (Is::settled($this->aggregate)) {
return;
}
+6 -4
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -13,9 +14,10 @@ class FulfilledPromise implements PromiseInterface
public function __construct($value)
{
if (method_exists($value, 'then')) {
if (is_object($value) && method_exists($value, 'then')) {
throw new \InvalidArgumentException(
'You cannot create a FulfilledPromise with a promise.');
'You cannot create a FulfilledPromise with a promise.'
);
}
$this->value = $value;
@@ -30,11 +32,11 @@ class FulfilledPromise implements PromiseInterface
return $this;
}
$queue = queue();
$queue = Utils::queue();
$p = new Promise([$queue, 'run']);
$value = $this->value;
$queue->add(static function () use ($p, $value, $onFulfilled) {
if ($p->getState() === self::PENDING) {
if (Is::pending($p)) {
try {
$p->resolve($onFulfilled($value));
} catch (\Throwable $e) {
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace GuzzleHttp\Promise;
final class Is
{
/**
* Returns true if a promise is pending.
*
* @return bool
*/
public static function pending(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::PENDING;
}
/**
* Returns true if a promise is fulfilled or rejected.
*
* @return bool
*/
public static function settled(PromiseInterface $promise)
{
return $promise->getState() !== PromiseInterface::PENDING;
}
/**
* Returns true if a promise is fulfilled.
*
* @return bool
*/
public static function fulfilled(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::FULFILLED;
}
/**
* Returns true if a promise is rejected.
*
* @return bool
*/
public static function rejected(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::REJECTED;
}
}
+33 -35
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -41,14 +42,13 @@ class Promise implements PromiseInterface
// Return a fulfilled promise and immediately invoke any callbacks.
if ($this->state === self::FULFILLED) {
return $onFulfilled
? promise_for($this->result)->then($onFulfilled)
: promise_for($this->result);
$promise = Create::promiseFor($this->result);
return $onFulfilled ? $promise->then($onFulfilled) : $promise;
}
// It's either cancelled or rejected, so return a rejected promise
// and immediately invoke any callbacks.
$rejection = rejection_for($this->result);
$rejection = Create::rejectionFor($this->result);
return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
}
@@ -61,19 +61,15 @@ class Promise implements PromiseInterface
{
$this->waitIfPending();
$inner = $this->result instanceof PromiseInterface
? $this->result->wait($unwrap)
: $this->result;
if ($this->result instanceof PromiseInterface) {
return $this->result->wait($unwrap);
}
if ($unwrap) {
if ($this->result instanceof PromiseInterface
|| $this->state === self::FULFILLED
) {
return $inner;
} else {
// It's rejected so "unwrap" and throw an exception.
throw exception_for($inner);
if ($this->state === self::FULFILLED) {
return $this->result;
}
// It's rejected so "unwrap" and throw an exception.
throw Create::exceptionFor($this->result);
}
}
@@ -103,6 +99,7 @@ class Promise implements PromiseInterface
}
// Reject the promise only if it wasn't rejected in a then callback.
/** @psalm-suppress RedundantCondition */
if ($this->state === self::PENDING) {
$this->reject(new CancellationException('Promise has been cancelled'));
}
@@ -148,17 +145,15 @@ class Promise implements PromiseInterface
// If the value was not a settled promise or a thenable, then resolve
// it in the task queue using the correct ID.
if (!method_exists($value, 'then')) {
if (!is_object($value) || !method_exists($value, 'then')) {
$id = $state === self::FULFILLED ? 1 : 2;
// It's a success, so resolve the handlers in the queue.
queue()->add(static function () use ($id, $value, $handlers) {
Utils::queue()->add(static function () use ($id, $value, $handlers) {
foreach ($handlers as $handler) {
self::callHandler($id, $value, $handler);
}
});
} elseif ($value instanceof Promise
&& $value->getState() === self::PENDING
) {
} elseif ($value instanceof Promise && Is::pending($value)) {
// We can just merge our handlers onto the next promise.
$value->handlers = array_merge($value->handlers, $handlers);
} else {
@@ -184,8 +179,6 @@ class Promise implements PromiseInterface
* @param int $index 1 (resolve) or 2 (reject).
* @param mixed $value Value to pass to the callback.
* @param array $handler Array of handler data (promise and callbacks).
*
* @return array Returns the next group to resolve.
*/
private static function callHandler($index, $value, array $handler)
{
@@ -194,13 +187,21 @@ class Promise implements PromiseInterface
// The promise may have been cancelled or resolved before placing
// this thunk in the queue.
if ($promise->getState() !== self::PENDING) {
if (Is::settled($promise)) {
return;
}
try {
if (isset($handler[$index])) {
$promise->resolve($handler[$index]($value));
/*
* If $f throws an exception, then $handler will be in the exception
* stack trace. Since $handler contains a reference to the callable
* itself we get a circular reference. We clear the $handler
* here to avoid that memory leak.
*/
$f = $handler[$index];
unset($handler);
$promise->resolve($f($value));
} elseif ($index === 1) {
// Forward resolution values as-is.
$promise->resolve($value);
@@ -224,15 +225,16 @@ class Promise implements PromiseInterface
} elseif ($this->waitList) {
$this->invokeWaitList();
} else {
// If there's not wait function, then reject the promise.
// If there's no wait function, then reject the promise.
$this->reject('Cannot wait on a promise that has '
. 'no internal wait function. You must provide a wait '
. 'function when constructing the promise to be able to '
. 'wait on a promise.');
}
queue()->run();
Utils::queue()->run();
/** @psalm-suppress RedundantCondition */
if ($this->state === self::PENDING) {
$this->reject('Invoking the wait callback did not resolve the promise');
}
@@ -263,17 +265,13 @@ class Promise implements PromiseInterface
$this->waitList = null;
foreach ($waitList as $result) {
while (true) {
do {
$result->waitIfPending();
$result = $result->result;
} while ($result instanceof Promise);
if ($result->result instanceof Promise) {
$result = $result->result;
} else {
if ($result->result instanceof PromiseInterface) {
$result->result->wait(false);
}
break;
}
if ($result instanceof PromiseInterface) {
$result->wait(false);
}
}
}
+4
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -56,6 +57,7 @@ interface PromiseInterface
* Resolve the promise with the given value.
*
* @param mixed $value
*
* @throws \RuntimeException if the promise is already resolved.
*/
public function resolve($value);
@@ -64,6 +66,7 @@ interface PromiseInterface
* Reject the promise with the given reason.
*
* @param mixed $reason
*
* @throws \RuntimeException if the promise is already resolved.
*/
public function reject($reason);
@@ -86,6 +89,7 @@ interface PromiseInterface
* @param bool $unwrap
*
* @return mixed
*
* @throws \LogicException if the promise has no wait function or if the
* promise does not settle after waiting.
*/
+1
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
+9 -5
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -13,9 +14,10 @@ class RejectedPromise implements PromiseInterface
public function __construct($reason)
{
if (method_exists($reason, 'then')) {
if (is_object($reason) && method_exists($reason, 'then')) {
throw new \InvalidArgumentException(
'You cannot create a RejectedPromise with a promise.');
'You cannot create a RejectedPromise with a promise.'
);
}
$this->reason = $reason;
@@ -30,11 +32,11 @@ class RejectedPromise implements PromiseInterface
return $this;
}
$queue = queue();
$queue = Utils::queue();
$reason = $this->reason;
$p = new Promise([$queue, 'run']);
$queue->add(static function () use ($p, $reason, $onRejected) {
if ($p->getState() === self::PENDING) {
if (Is::pending($p)) {
try {
// Return a resolved promise if onRejected does not throw.
$p->resolve($onRejected($reason));
@@ -59,8 +61,10 @@ class RejectedPromise implements PromiseInterface
public function wait($unwrap = true, $defaultDelivery = null)
{
if ($unwrap) {
throw exception_for($this->reason);
throw Create::exceptionFor($this->reason);
}
return null;
}
public function getState()
+2 -1
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -12,7 +13,7 @@ class RejectionException extends \RuntimeException
private $reason;
/**
* @param mixed $reason Rejection reason.
* @param mixed $reason Rejection reason.
* @param string $description Optional description
*/
public function __construct($reason, $description = null)
+3 -2
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -8,7 +9,7 @@ namespace GuzzleHttp\Promise;
* maintains a constant stack size. You can use the task queue asynchronously
* by calling the `run()` function of the global task queue in an event loop.
*
* GuzzleHttp\Promise\queue()->run();
* GuzzleHttp\Promise\Utils::queue()->run();
*/
class TaskQueue implements TaskQueueInterface
{
@@ -42,8 +43,8 @@ class TaskQueue implements TaskQueueInterface
public function run()
{
/** @var callable $task */
while ($task = array_shift($this->queue)) {
/** @var callable $task */
$task();
}
}
+1 -2
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
interface TaskQueueInterface
@@ -13,8 +14,6 @@ interface TaskQueueInterface
/**
* Adds a task to the queue that will be executed the next time run is
* called.
*
* @param callable $task
*/
public function add(callable $task);
+274
View File
@@ -0,0 +1,274 @@
<?php
namespace GuzzleHttp\Promise;
final class Utils
{
/**
* Get the global task queue used for promise resolution.
*
* This task queue MUST be run in an event loop in order for promises to be
* settled asynchronously. It will be automatically run when synchronously
* waiting on a promise.
*
* <code>
* while ($eventLoop->isRunning()) {
* GuzzleHttp\Promise\Utils::queue()->run();
* }
* </code>
*
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
*/
public static function queue(TaskQueueInterface $assign = null)
{
static $queue;
if ($assign) {
$queue = $assign;
} elseif (!$queue) {
$queue = new TaskQueue();
}
return $queue;
}
/**
* Adds a function to run in the task queue when it is next `run()` and
* returns a promise that is fulfilled or rejected with the result.
*
* @param callable $task Task function to run.
*
* @return PromiseInterface
*/
public static function task(callable $task)
{
$queue = self::queue();
$promise = new Promise([$queue, 'run']);
$queue->add(function () use ($task, $promise) {
try {
$promise->resolve($task());
} catch (\Throwable $e) {
$promise->reject($e);
} catch (\Exception $e) {
$promise->reject($e);
}
});
return $promise;
}
/**
* Synchronously waits on a promise to resolve and returns an inspection
* state array.
*
* Returns a state associative array containing a "state" key mapping to a
* valid promise state. If the state of the promise is "fulfilled", the
* array will contain a "value" key mapping to the fulfilled value of the
* promise. If the promise is rejected, the array will contain a "reason"
* key mapping to the rejection reason of the promise.
*
* @param PromiseInterface $promise Promise or value.
*
* @return array
*/
public static function inspect(PromiseInterface $promise)
{
try {
return [
'state' => PromiseInterface::FULFILLED,
'value' => $promise->wait()
];
} catch (RejectionException $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
} catch (\Throwable $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
} catch (\Exception $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
}
}
/**
* Waits on all of the provided promises, but does not unwrap rejected
* promises as thrown exception.
*
* Returns an array of inspection state arrays.
*
* @see inspect for the inspection state array format.
*
* @param PromiseInterface[] $promises Traversable of promises to wait upon.
*
* @return array
*/
public static function inspectAll($promises)
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = inspect($promise);
}
return $results;
}
/**
* Waits on all of the provided promises and returns the fulfilled values.
*
* Returns an array that contains the value of each promise (in the same
* order the promises were provided). An exception is thrown if any of the
* promises are rejected.
*
* @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
*
* @return array
*
* @throws \Exception on error
* @throws \Throwable on error in PHP >=7
*/
public static function unwrap($promises)
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = $promise->wait();
}
return $results;
}
/**
* Given an array of promises, return a promise that is fulfilled when all
* the items in the array are fulfilled.
*
* The promise's fulfillment value is an array with fulfillment values at
* respective positions to the original array. If any promise in the array
* rejects, the returned promise is rejected with the rejection reason.
*
* @param mixed $promises Promises or values.
* @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
*
* @return PromiseInterface
*/
public static function all($promises, $recursive = false)
{
$results = [];
$promise = Each::of(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = $value;
},
function ($reason, $idx, Promise $aggregate) {
$aggregate->reject($reason);
}
)->then(function () use (&$results) {
ksort($results);
return $results;
});
if (true === $recursive) {
$promise = $promise->then(function ($results) use ($recursive, &$promises) {
foreach ($promises as $promise) {
if (Is::pending($promise)) {
return self::all($promises, $recursive);
}
}
return $results;
});
}
return $promise;
}
/**
* Initiate a competitive race between multiple promises or values (values
* will become immediately fulfilled promises).
*
* When count amount of promises have been fulfilled, the returned promise
* is fulfilled with an array that contains the fulfillment values of the
* winners in order of resolution.
*
* This promise is rejected with a {@see AggregateException} if the number
* of fulfilled promises is less than the desired $count.
*
* @param int $count Total number of promises.
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*/
public static function some($count, $promises)
{
$results = [];
$rejections = [];
return Each::of(
$promises,
function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
if (Is::settled($p)) {
return;
}
$results[$idx] = $value;
if (count($results) >= $count) {
$p->resolve(null);
}
},
function ($reason) use (&$rejections) {
$rejections[] = $reason;
}
)->then(
function () use (&$results, &$rejections, $count) {
if (count($results) !== $count) {
throw new AggregateException(
'Not enough promises to fulfill count',
$rejections
);
}
ksort($results);
return array_values($results);
}
);
}
/**
* Like some(), with 1 as count. However, if the promise fulfills, the
* fulfillment value is not an array of 1 but the value directly.
*
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*/
public static function any($promises)
{
return self::some(1, $promises)->then(function ($values) {
return $values[0];
});
}
/**
* Returns a promise that is fulfilled when all of the provided promises have
* been fulfilled or rejected.
*
* The returned promise is fulfilled with an array of inspection state arrays.
*
* @see inspect for the inspection state array format.
*
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*/
public static function settle($promises)
{
$results = [];
return Each::of(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
},
function ($reason, $idx) use (&$results) {
$results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
}
)->then(function () use (&$results) {
ksort($results);
return $results;
});
}
}
+80 -174
View File
@@ -1,4 +1,5 @@
<?php
namespace GuzzleHttp\Promise;
/**
@@ -17,18 +18,12 @@ namespace GuzzleHttp\Promise;
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
*
* @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead.
*/
function queue(TaskQueueInterface $assign = null)
{
static $queue;
if ($assign) {
$queue = $assign;
} elseif (!$queue) {
$queue = new TaskQueue();
}
return $queue;
return Utils::queue($assign);
}
/**
@@ -38,22 +33,12 @@ function queue(TaskQueueInterface $assign = null)
* @param callable $task Task function to run.
*
* @return PromiseInterface
*
* @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead.
*/
function task(callable $task)
{
$queue = queue();
$promise = new Promise([$queue, 'run']);
$queue->add(function () use ($task, $promise) {
try {
$promise->resolve($task());
} catch (\Throwable $e) {
$promise->reject($e);
} catch (\Exception $e) {
$promise->reject($e);
}
});
return $promise;
return Utils::task($task);
}
/**
@@ -62,23 +47,12 @@ function task(callable $task)
* @param mixed $value Promise or value.
*
* @return PromiseInterface
*
* @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead.
*/
function promise_for($value)
{
if ($value instanceof PromiseInterface) {
return $value;
}
// Return a Guzzle promise that shadows the given promise.
if (method_exists($value, 'then')) {
$wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
$cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
$promise = new Promise($wfn, $cfn);
$value->then([$promise, 'resolve'], [$promise, 'reject']);
return $promise;
}
return new FulfilledPromise($value);
return Create::promiseFor($value);
}
/**
@@ -88,14 +62,12 @@ function promise_for($value)
* @param mixed $reason Promise or reason.
*
* @return PromiseInterface
*
* @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead.
*/
function rejection_for($reason)
{
if ($reason instanceof PromiseInterface) {
return $reason;
}
return new RejectedPromise($reason);
return Create::rejectionFor($reason);
}
/**
@@ -104,12 +76,12 @@ function rejection_for($reason)
* @param mixed $reason
*
* @return \Exception|\Throwable
*
* @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead.
*/
function exception_for($reason)
{
return $reason instanceof \Exception || $reason instanceof \Throwable
? $reason
: new RejectionException($reason);
return Create::exceptionFor($reason);
}
/**
@@ -118,16 +90,12 @@ function exception_for($reason)
* @param mixed $value
*
* @return \Iterator
*
* @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead.
*/
function iter_for($value)
{
if ($value instanceof \Iterator) {
return $value;
} elseif (is_array($value)) {
return new \ArrayIterator($value);
} else {
return new \ArrayIterator([$value]);
}
return Create::iterFor($value);
}
/**
@@ -143,21 +111,12 @@ function iter_for($value)
* @param PromiseInterface $promise Promise or value.
*
* @return array
*
* @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead.
*/
function inspect(PromiseInterface $promise)
{
try {
return [
'state' => PromiseInterface::FULFILLED,
'value' => $promise->wait()
];
} catch (RejectionException $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
} catch (\Throwable $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
} catch (\Exception $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
}
return Utils::inspect($promise);
}
/**
@@ -166,19 +125,17 @@ function inspect(PromiseInterface $promise)
*
* Returns an array of inspection state arrays.
*
* @see inspect for the inspection state array format.
*
* @param PromiseInterface[] $promises Traversable of promises to wait upon.
*
* @return array
* @see GuzzleHttp\Promise\inspect for the inspection state array format.
*
* @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead.
*/
function inspect_all($promises)
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = inspect($promise);
}
return $results;
return Utils::inspectAll($promises);
}
/**
@@ -188,20 +145,18 @@ function inspect_all($promises)
* the promises were provided). An exception is thrown if any of the promises
* are rejected.
*
* @param mixed $promises Iterable of PromiseInterface objects to wait on.
* @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
*
* @return array
*
* @throws \Exception on error
* @throws \Throwable on error in PHP >=7
*
* @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead.
*/
function unwrap($promises)
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = $promise->wait();
}
return $results;
return Utils::unwrap($promises);
}
/**
@@ -212,25 +167,16 @@ function unwrap($promises)
* respective positions to the original array. If any promise in the array
* rejects, the returned promise is rejected with the rejection reason.
*
* @param mixed $promises Promises or values.
* @param mixed $promises Promises or values.
* @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
*
* @return PromiseInterface
*
* @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead.
*/
function all($promises)
function all($promises, $recursive = false)
{
$results = [];
return each(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = $value;
},
function ($reason, $idx, Promise $aggregate) {
$aggregate->reject($reason);
}
)->then(function () use (&$results) {
ksort($results);
return $results;
});
return Utils::all($promises, $recursive);
}
/**
@@ -241,45 +187,19 @@ function all($promises)
* fulfilled with an array that contains the fulfillment values of the winners
* in order of resolution.
*
* This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
* if the number of fulfilled promises is less than the desired $count.
* This promise is rejected with a {@see AggregateException} if the number of
* fulfilled promises is less than the desired $count.
*
* @param int $count Total number of promises.
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*
* @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead.
*/
function some($count, $promises)
{
$results = [];
$rejections = [];
return each(
$promises,
function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
if ($p->getState() !== PromiseInterface::PENDING) {
return;
}
$results[$idx] = $value;
if (count($results) >= $count) {
$p->resolve(null);
}
},
function ($reason) use (&$rejections) {
$rejections[] = $reason;
}
)->then(
function () use (&$results, &$rejections, $count) {
if (count($results) !== $count) {
throw new AggregateException(
'Not enough promises to fulfill count',
$rejections
);
}
ksort($results);
return array_values($results);
}
);
return Utils::some($count, $promises);
}
/**
@@ -289,10 +209,12 @@ function some($count, $promises)
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
*
* @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead.
*/
function any($promises)
{
return some(1, $promises)->then(function ($values) { return $values[0]; });
return Utils::any($promises);
}
/**
@@ -301,27 +223,17 @@ function any($promises)
*
* The returned promise is fulfilled with an array of inspection state arrays.
*
* @see inspect for the inspection state array format.
*
* @param mixed $promises Promises or values.
*
* @return PromiseInterface
* @see GuzzleHttp\Promise\inspect for the inspection state array format.
*
* @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead.
*/
function settle($promises)
{
$results = [];
return each(
$promises,
function ($value, $idx) use (&$results) {
$results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
},
function ($reason, $idx) use (&$results) {
$results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
}
)->then(function () use (&$results) {
ksort($results);
return $results;
});
return Utils::settle($promises);
}
/**
@@ -329,29 +241,28 @@ function settle($promises)
* fulfilled with a null value when the iterator has been consumed or the
* aggregate promise has been fulfilled or rejected.
*
* $onFulfilled is a function that accepts the fulfilled value, iterator
* index, and the aggregate promise. The callback can invoke any necessary side
* effects and choose to resolve or reject the aggregate promise if needed.
* $onFulfilled is a function that accepts the fulfilled value, iterator index,
* and the aggregate promise. The callback can invoke any necessary side
* effects and choose to resolve or reject the aggregate if needed.
*
* $onRejected is a function that accepts the rejection reason, iterator
* index, and the aggregate promise. The callback can invoke any necessary side
* effects and choose to resolve or reject the aggregate promise if needed.
* $onRejected is a function that accepts the rejection reason, iterator index,
* and the aggregate promise. The callback can invoke any necessary side
* effects and choose to resolve or reject the aggregate if needed.
*
* @param mixed $iterable Iterator or array to iterate over.
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*
* @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead.
*/
function each(
$iterable,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected
]))->promise();
return Each::of($iterable, $onFulfilled, $onRejected);
}
/**
@@ -368,6 +279,8 @@ function each(
* @param callable $onRejected
*
* @return PromiseInterface
*
* @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead.
*/
function each_limit(
$iterable,
@@ -375,11 +288,7 @@ function each_limit(
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected,
'concurrency' => $concurrency
]))->promise();
return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected);
}
/**
@@ -392,66 +301,63 @@ function each_limit(
* @param callable $onFulfilled
*
* @return PromiseInterface
*
* @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead.
*/
function each_limit_all(
$iterable,
$concurrency,
callable $onFulfilled = null
) {
return each_limit(
$iterable,
$concurrency,
$onFulfilled,
function ($reason, $idx, PromiseInterface $aggregate) {
$aggregate->reject($reason);
}
);
return Each::ofLimitAll($iterable, $concurrency, $onFulfilled);
}
/**
* Returns true if a promise is fulfilled.
*
* @param PromiseInterface $promise
*
* @return bool
*
* @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead.
*/
function is_fulfilled(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::FULFILLED;
return Is::fulfilled($promise);
}
/**
* Returns true if a promise is rejected.
*
* @param PromiseInterface $promise
*
* @return bool
*
* @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead.
*/
function is_rejected(PromiseInterface $promise)
{
return $promise->getState() === PromiseInterface::REJECTED;
return Is::rejected($promise);
}
/**
* Returns true if a promise is fulfilled or rejected.
*
* @param PromiseInterface $promise
*
* @return bool
*
* @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead.
*/
function is_settled(PromiseInterface $promise)
{
return $promise->getState() !== PromiseInterface::PENDING;
return Is::settled($promise);
}
/**
* Create a new coroutine.
*
* @see Coroutine
*
* @param callable $generatorFn
*
* @return PromiseInterface
*
* @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead.
*/
function coroutine(callable $generatorFn)
{
return new Coroutine($generatorFn);
return Coroutine::of($generatorFn);
}