PluginProbe
StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more / 2.4.1
StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more v2.4.1
3.7.2 3.7.1 3.7.0 3.6.0 3.6.1 3.5.2 trunk 2.0 2.1 2.2 2.3 2.4 2.4.1 2.4.2 2.4.3 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.6.0 2.6.1 All 73 releases
stockpack / vendor / guzzlehttp / promises / src / Promise.php

Promise.php in StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more 2.4.1, at vendor/guzzlehttp/promises/src/Promise.php

281 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 no 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 }
281