170 lines
4.2 KiB
PHP
170 lines
4.2 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace GuzzleHttp\Promise;
|
||
|
|
||
|
use Exception;
|
||
|
use Generator;
|
||
|
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::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.
|
||
|
* This can lead to less verbose code when doing lots of sequential async calls
|
||
|
* with minimal processing in between.
|
||
|
*
|
||
|
* use GuzzleHttp\Promise;
|
||
|
*
|
||
|
* function createPromise($value) {
|
||
|
* return new Promise\FulfilledPromise($value);
|
||
|
* }
|
||
|
*
|
||
|
* $promise = Promise\Coroutine::of(function () {
|
||
|
* $value = (yield createPromise('a'));
|
||
|
* try {
|
||
|
* $value = (yield createPromise($value . 'b'));
|
||
|
* } catch (\Exception $e) {
|
||
|
* // The promise was rejected.
|
||
|
* }
|
||
|
* yield $value . 'c';
|
||
|
* });
|
||
|
*
|
||
|
* // Outputs "abc"
|
||
|
* $promise->then(function ($v) { echo $v; });
|
||
|
*
|
||
|
* @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
|
||
|
{
|
||
|
/**
|
||
|
* @var PromiseInterface|null
|
||
|
*/
|
||
|
private $currentPromise;
|
||
|
|
||
|
/**
|
||
|
* @var Generator
|
||
|
*/
|
||
|
private $generator;
|
||
|
|
||
|
/**
|
||
|
* @var Promise
|
||
|
*/
|
||
|
private $result;
|
||
|
|
||
|
public function __construct(callable $generatorFn)
|
||
|
{
|
||
|
$this->generator = $generatorFn();
|
||
|
$this->result = new Promise(function () {
|
||
|
while (isset($this->currentPromise)) {
|
||
|
$this->currentPromise->wait();
|
||
|
}
|
||
|
});
|
||
|
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(
|
||
|
callable $onFulfilled = null,
|
||
|
callable $onRejected = null
|
||
|
) {
|
||
|
return $this->result->then($onFulfilled, $onRejected);
|
||
|
}
|
||
|
|
||
|
public function otherwise(callable $onRejected)
|
||
|
{
|
||
|
return $this->result->otherwise($onRejected);
|
||
|
}
|
||
|
|
||
|
public function wait($unwrap = true)
|
||
|
{
|
||
|
return $this->result->wait($unwrap);
|
||
|
}
|
||
|
|
||
|
public function getState()
|
||
|
{
|
||
|
return $this->result->getState();
|
||
|
}
|
||
|
|
||
|
public function resolve($value)
|
||
|
{
|
||
|
$this->result->resolve($value);
|
||
|
}
|
||
|
|
||
|
public function reject($reason)
|
||
|
{
|
||
|
$this->result->reject($reason);
|
||
|
}
|
||
|
|
||
|
public function cancel()
|
||
|
{
|
||
|
$this->currentPromise->cancel();
|
||
|
$this->result->cancel();
|
||
|
}
|
||
|
|
||
|
private function nextCoroutine($yielded)
|
||
|
{
|
||
|
$this->currentPromise = Create::promiseFor($yielded)
|
||
|
->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
public function _handleSuccess($value)
|
||
|
{
|
||
|
unset($this->currentPromise);
|
||
|
try {
|
||
|
$next = $this->generator->send($value);
|
||
|
if ($this->generator->valid()) {
|
||
|
$this->nextCoroutine($next);
|
||
|
} else {
|
||
|
$this->result->resolve($value);
|
||
|
}
|
||
|
} catch (Exception $exception) {
|
||
|
$this->result->reject($exception);
|
||
|
} catch (Throwable $throwable) {
|
||
|
$this->result->reject($throwable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
public function _handleFailure($reason)
|
||
|
{
|
||
|
unset($this->currentPromise);
|
||
|
try {
|
||
|
$nextYield = $this->generator->throw(Create::exceptionFor($reason));
|
||
|
// The throw was caught, so keep iterating on the coroutine
|
||
|
$this->nextCoroutine($nextYield);
|
||
|
} catch (Exception $exception) {
|
||
|
$this->result->reject($exception);
|
||
|
} catch (Throwable $throwable) {
|
||
|
$this->result->reject($throwable);
|
||
|
}
|
||
|
}
|
||
|
}
|