Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracted functions to static methods #113

Merged
merged 2 commits into from
Sep 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# CHANGELOG


## Upcoming

### Added

- 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


## 1.3.1 - 2016-12-20

### Fixed
Expand Down
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!');
});
Expand Down Expand Up @@ -220,7 +220,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.
Expand Down Expand Up @@ -397,7 +397,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
Expand All @@ -424,7 +424,7 @@ instance.

```php
// Get the global task queue
$queue = \GuzzleHttp\Promise\queue();
$queue = GuzzleHttp\Promise\Utils::queue();
$queue->run();
```

Expand Down Expand Up @@ -502,3 +502,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` |
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
"dev-master": "1.4-dev"
}
}
}
1 change: 1 addition & 0 deletions src/AggregateException.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace GuzzleHttp\Promise;

/**
Expand Down
1 change: 1 addition & 0 deletions src/CancellationException.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace GuzzleHttp\Promise;

/**
Expand Down
19 changes: 15 additions & 4 deletions src/Coroutine.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace GuzzleHttp\Promise;

use Exception;
Expand All @@ -9,7 +10,7 @@
* 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.
Expand All @@ -22,7 +23,7 @@
* return new Promise\FulfilledPromise($value);
* }
*
* $promise = Promise\coroutine(function () {
* $promise = Promise\Coroutine::of(function () {
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
Expand Down Expand Up @@ -74,6 +75,16 @@ public function __construct(callable $generatorFn)
}
}

/**
* Create a new coroutine.
*
* @return self
*/
public static function of(callable $generatorFn)
{
return new self($generatorFn);
}

public function then(
callable $onFulfilled = null,
callable $onRejected = null
Expand Down Expand Up @@ -114,7 +125,7 @@ public function cancel()

private function nextCoroutine($yielded)
{
$this->currentPromise = promise_for($yielded)
$this->currentPromise = Create::promiseFor($yielded)
->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
}

Expand Down Expand Up @@ -145,7 +156,7 @@ public function _handleFailure($reason)
{
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) {
Expand Down
84 changes: 84 additions & 0 deletions src/Create.php
Original file line number Diff line number Diff line change
@@ -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 changes: 90 additions & 0 deletions src/Each.php
Original file line number Diff line number Diff line change
@@ -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);
}
);
}
}
9 changes: 5 additions & 4 deletions src/EachPromise.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace GuzzleHttp\Promise;

/**
Expand Down Expand Up @@ -50,7 +51,7 @@ class EachPromise implements PromisorInterface
*/
public function __construct($iterable, array $config = [])
{
$this->iterable = iter_for($iterable);
$this->iterable = Create::iterFor($iterable);

if (isset($config['concurrency'])) {
$this->concurrency = $config['concurrency'];
Expand Down Expand Up @@ -96,7 +97,7 @@ private function createPromise()
while ($promise = current($this->pending)) {
next($this->pending);
$promise->wait();
if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
if (Is::settled($this->aggregate)) {
return;
}
}
Expand Down Expand Up @@ -145,7 +146,7 @@ private function addPending()
return false;
}

$promise = promise_for($this->iterable->current());
$promise = Create::promiseFor($this->iterable->current());
$key = $this->iterable->key();

// Iterable keys may not be unique, so we add the promises at the end
Expand Down Expand Up @@ -204,7 +205,7 @@ private function advanceIterator()
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;
}

Expand Down