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