Mercurial > hg > isophonics-drupal-site
comparison vendor/guzzlehttp/promises/src/Promise.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 namespace GuzzleHttp\Promise; | |
3 | |
4 /** | |
5 * Promises/A+ implementation that avoids recursion when possible. | |
6 * | |
7 * @link https://promisesaplus.com/ | |
8 */ | |
9 class Promise implements PromiseInterface | |
10 { | |
11 private $state = self::PENDING; | |
12 private $result; | |
13 private $cancelFn; | |
14 private $waitFn; | |
15 private $waitList; | |
16 private $handlers = []; | |
17 | |
18 /** | |
19 * @param callable $waitFn Fn that when invoked resolves the promise. | |
20 * @param callable $cancelFn Fn that when invoked cancels the promise. | |
21 */ | |
22 public function __construct( | |
23 callable $waitFn = null, | |
24 callable $cancelFn = null | |
25 ) { | |
26 $this->waitFn = $waitFn; | |
27 $this->cancelFn = $cancelFn; | |
28 } | |
29 | |
30 public function then( | |
31 callable $onFulfilled = null, | |
32 callable $onRejected = null | |
33 ) { | |
34 if ($this->state === self::PENDING) { | |
35 $p = new Promise(null, [$this, 'cancel']); | |
36 $this->handlers[] = [$p, $onFulfilled, $onRejected]; | |
37 $p->waitList = $this->waitList; | |
38 $p->waitList[] = $this; | |
39 return $p; | |
40 } | |
41 | |
42 // Return a fulfilled promise and immediately invoke any callbacks. | |
43 if ($this->state === self::FULFILLED) { | |
44 return $onFulfilled | |
45 ? promise_for($this->result)->then($onFulfilled) | |
46 : promise_for($this->result); | |
47 } | |
48 | |
49 // It's either cancelled or rejected, so return a rejected promise | |
50 // and immediately invoke any callbacks. | |
51 $rejection = rejection_for($this->result); | |
52 return $onRejected ? $rejection->then(null, $onRejected) : $rejection; | |
53 } | |
54 | |
55 public function otherwise(callable $onRejected) | |
56 { | |
57 return $this->then(null, $onRejected); | |
58 } | |
59 | |
60 public function wait($unwrap = true) | |
61 { | |
62 $this->waitIfPending(); | |
63 | |
64 $inner = $this->result instanceof PromiseInterface | |
65 ? $this->result->wait($unwrap) | |
66 : $this->result; | |
67 | |
68 if ($unwrap) { | |
69 if ($this->result instanceof PromiseInterface | |
70 || $this->state === self::FULFILLED | |
71 ) { | |
72 return $inner; | |
73 } else { | |
74 // It's rejected so "unwrap" and throw an exception. | |
75 throw exception_for($inner); | |
76 } | |
77 } | |
78 } | |
79 | |
80 public function getState() | |
81 { | |
82 return $this->state; | |
83 } | |
84 | |
85 public function cancel() | |
86 { | |
87 if ($this->state !== self::PENDING) { | |
88 return; | |
89 } | |
90 | |
91 $this->waitFn = $this->waitList = null; | |
92 | |
93 if ($this->cancelFn) { | |
94 $fn = $this->cancelFn; | |
95 $this->cancelFn = null; | |
96 try { | |
97 $fn(); | |
98 } catch (\Throwable $e) { | |
99 $this->reject($e); | |
100 } catch (\Exception $e) { | |
101 $this->reject($e); | |
102 } | |
103 } | |
104 | |
105 // Reject the promise only if it wasn't rejected in a then callback. | |
106 if ($this->state === self::PENDING) { | |
107 $this->reject(new CancellationException('Promise has been cancelled')); | |
108 } | |
109 } | |
110 | |
111 public function resolve($value) | |
112 { | |
113 $this->settle(self::FULFILLED, $value); | |
114 } | |
115 | |
116 public function reject($reason) | |
117 { | |
118 $this->settle(self::REJECTED, $reason); | |
119 } | |
120 | |
121 private function settle($state, $value) | |
122 { | |
123 if ($this->state !== self::PENDING) { | |
124 // Ignore calls with the same resolution. | |
125 if ($state === $this->state && $value === $this->result) { | |
126 return; | |
127 } | |
128 throw $this->state === $state | |
129 ? new \LogicException("The promise is already {$state}.") | |
130 : new \LogicException("Cannot change a {$this->state} promise to {$state}"); | |
131 } | |
132 | |
133 if ($value === $this) { | |
134 throw new \LogicException('Cannot fulfill or reject a promise with itself'); | |
135 } | |
136 | |
137 // Clear out the state of the promise but stash the handlers. | |
138 $this->state = $state; | |
139 $this->result = $value; | |
140 $handlers = $this->handlers; | |
141 $this->handlers = null; | |
142 $this->waitList = $this->waitFn = null; | |
143 $this->cancelFn = null; | |
144 | |
145 if (!$handlers) { | |
146 return; | |
147 } | |
148 | |
149 // If the value was not a settled promise or a thenable, then resolve | |
150 // it in the task queue using the correct ID. | |
151 if (!method_exists($value, 'then')) { | |
152 $id = $state === self::FULFILLED ? 1 : 2; | |
153 // It's a success, so resolve the handlers in the queue. | |
154 queue()->add(static function () use ($id, $value, $handlers) { | |
155 foreach ($handlers as $handler) { | |
156 self::callHandler($id, $value, $handler); | |
157 } | |
158 }); | |
159 } elseif ($value instanceof Promise | |
160 && $value->getState() === self::PENDING | |
161 ) { | |
162 // We can just merge our handlers onto the next promise. | |
163 $value->handlers = array_merge($value->handlers, $handlers); | |
164 } else { | |
165 // Resolve the handlers when the forwarded promise is resolved. | |
166 $value->then( | |
167 static function ($value) use ($handlers) { | |
168 foreach ($handlers as $handler) { | |
169 self::callHandler(1, $value, $handler); | |
170 } | |
171 }, | |
172 static function ($reason) use ($handlers) { | |
173 foreach ($handlers as $handler) { | |
174 self::callHandler(2, $reason, $handler); | |
175 } | |
176 } | |
177 ); | |
178 } | |
179 } | |
180 | |
181 /** | |
182 * Call a stack of handlers using a specific callback index and value. | |
183 * | |
184 * @param int $index 1 (resolve) or 2 (reject). | |
185 * @param mixed $value Value to pass to the callback. | |
186 * @param array $handler Array of handler data (promise and callbacks). | |
187 * | |
188 * @return array Returns the next group to resolve. | |
189 */ | |
190 private static function callHandler($index, $value, array $handler) | |
191 { | |
192 /** @var PromiseInterface $promise */ | |
193 $promise = $handler[0]; | |
194 | |
195 // The promise may have been cancelled or resolved before placing | |
196 // this thunk in the queue. | |
197 if ($promise->getState() !== self::PENDING) { | |
198 return; | |
199 } | |
200 | |
201 try { | |
202 if (isset($handler[$index])) { | |
203 $promise->resolve($handler[$index]($value)); | |
204 } elseif ($index === 1) { | |
205 // Forward resolution values as-is. | |
206 $promise->resolve($value); | |
207 } else { | |
208 // Forward rejections down the chain. | |
209 $promise->reject($value); | |
210 } | |
211 } catch (\Throwable $reason) { | |
212 $promise->reject($reason); | |
213 } catch (\Exception $reason) { | |
214 $promise->reject($reason); | |
215 } | |
216 } | |
217 | |
218 private function waitIfPending() | |
219 { | |
220 if ($this->state !== self::PENDING) { | |
221 return; | |
222 } elseif ($this->waitFn) { | |
223 $this->invokeWaitFn(); | |
224 } elseif ($this->waitList) { | |
225 $this->invokeWaitList(); | |
226 } else { | |
227 // If there's not wait function, then reject the promise. | |
228 $this->reject('Cannot wait on a promise that has ' | |
229 . 'no internal wait function. You must provide a wait ' | |
230 . 'function when constructing the promise to be able to ' | |
231 . 'wait on a promise.'); | |
232 } | |
233 | |
234 queue()->run(); | |
235 | |
236 if ($this->state === self::PENDING) { | |
237 $this->reject('Invoking the wait callback did not resolve the promise'); | |
238 } | |
239 } | |
240 | |
241 private function invokeWaitFn() | |
242 { | |
243 try { | |
244 $wfn = $this->waitFn; | |
245 $this->waitFn = null; | |
246 $wfn(true); | |
247 } catch (\Exception $reason) { | |
248 if ($this->state === self::PENDING) { | |
249 // The promise has not been resolved yet, so reject the promise | |
250 // with the exception. | |
251 $this->reject($reason); | |
252 } else { | |
253 // The promise was already resolved, so there's a problem in | |
254 // the application. | |
255 throw $reason; | |
256 } | |
257 } | |
258 } | |
259 | |
260 private function invokeWaitList() | |
261 { | |
262 $waitList = $this->waitList; | |
263 $this->waitList = null; | |
264 | |
265 foreach ($waitList as $result) { | |
266 while (true) { | |
267 $result->waitIfPending(); | |
268 | |
269 if ($result->result instanceof Promise) { | |
270 $result = $result->result; | |
271 } else { | |
272 if ($result->result instanceof PromiseInterface) { | |
273 $result->result->wait(false); | |
274 } | |
275 break; | |
276 } | |
277 } | |
278 } | |
279 } | |
280 } |