krz/vox-populi
A Tumblr web client.
clone: git clone https://gitbay.org/krz/vox-populi.git
main: vendor/guzzlehttp/promises/src/Coroutine.php · raw
1<?php
2namespace GuzzleHttp\Promise;
3
4use Exception;
5use Generator;
6use Throwable;
7
8/**
9 * Creates a promise that is resolved using a generator that yields values or
10 * promises (somewhat similar to C#'s async keyword).
11 *
12 * When called, the coroutine function will start an instance of the generator
13 * and returns a promise that is fulfilled with its final yielded value.
14 *
15 * Control is returned back to the generator when the yielded promise settles.
16 * This can lead to less verbose code when doing lots of sequential async calls
17 * with minimal processing in between.
18 *
19 * use GuzzleHttp\Promise;
20 *
21 * function createPromise($value) {
22 * return new Promise\FulfilledPromise($value);
23 * }
24 *
25 * $promise = Promise\coroutine(function () {
26 * $value = (yield createPromise('a'));
27 * try {
28 * $value = (yield createPromise($value . 'b'));
29 * } catch (\Exception $e) {
30 * // The promise was rejected.
31 * }
32 * yield $value . 'c';
33 * });
34 *
35 * // Outputs "abc"
36 * $promise->then(function ($v) { echo $v; });
37 *
38 * @param callable $generatorFn Generator function to wrap into a promise.
39 *
40 * @return Promise
41 * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
42 */
43final class Coroutine implements PromiseInterface
44{
45 /**
46 * @var PromiseInterface|null
47 */
48 private $currentPromise;
49
50 /**
51 * @var Generator
52 */
53 private $generator;
54
55 /**
56 * @var Promise
57 */
58 private $result;
59
60 public function __construct(callable $generatorFn)
61 {
62 $this->generator = $generatorFn();
63 $this->result = new Promise(function () {
64 while (isset($this->currentPromise)) {
65 $this->currentPromise->wait();
66 }
67 });
68 $this->nextCoroutine($this->generator->current());
69 }
70
71 public function then(
72 callable $onFulfilled = null,
73 callable $onRejected = null
74 ) {
75 return $this->result->then($onFulfilled, $onRejected);
76 }
77
78 public function otherwise(callable $onRejected)
79 {
80 return $this->result->otherwise($onRejected);
81 }
82
83 public function wait($unwrap = true)
84 {
85 return $this->result->wait($unwrap);
86 }
87
88 public function getState()
89 {
90 return $this->result->getState();
91 }
92
93 public function resolve($value)
94 {
95 $this->result->resolve($value);
96 }
97
98 public function reject($reason)
99 {
100 $this->result->reject($reason);
101 }
102
103 public function cancel()
104 {
105 $this->currentPromise->cancel();
106 $this->result->cancel();
107 }
108
109 private function nextCoroutine($yielded)
110 {
111 $this->currentPromise = promise_for($yielded)
112 ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
113 }
114
115 /**
116 * @internal
117 */
118 public function _handleSuccess($value)
119 {
120 unset($this->currentPromise);
121 try {
122 $next = $this->generator->send($value);
123 if ($this->generator->valid()) {
124 $this->nextCoroutine($next);
125 } else {
126 $this->result->resolve($value);
127 }
128 } catch (Exception $exception) {
129 $this->result->reject($exception);
130 } catch (Throwable $throwable) {
131 $this->result->reject($throwable);
132 }
133 }
134
135 /**
136 * @internal
137 */
138 public function _handleFailure($reason)
139 {
140 unset($this->currentPromise);
141 try {
142 $nextYield = $this->generator->throw(exception_for($reason));
143 // The throw was caught, so keep iterating on the coroutine
144 $this->nextCoroutine($nextYield);
145 } catch (Exception $exception) {
146 $this->result->reject($exception);
147 } catch (Throwable $throwable) {
148 $this->result->reject($throwable);
149 }
150 }
151}