source: vendor/guzzlehttp/promises/src/Promise.php

Last change on this file was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

  • Property mode set to 100644
File size: 8.8 KB
RevLine 
[e3d4e0a]1<?php
2
3declare(strict_types=1);
4
5namespace GuzzleHttp\Promise;
6
7/**
8 * Promises/A+ implementation that avoids recursion when possible.
9 *
10 * @see https://promisesaplus.com/
11 *
12 * @final
13 */
14class Promise implements PromiseInterface
15{
16 private $state = self::PENDING;
17 private $result;
18 private $cancelFn;
19 private $waitFn;
20 private $waitList;
21 private $handlers = [];
22
23 /**
24 * @param callable $waitFn Fn that when invoked resolves the promise.
25 * @param callable $cancelFn Fn that when invoked cancels the promise.
26 */
27 public function __construct(
28 ?callable $waitFn = null,
29 ?callable $cancelFn = null
30 ) {
31 $this->waitFn = $waitFn;
32 $this->cancelFn = $cancelFn;
33 }
34
35 public function then(
36 ?callable $onFulfilled = null,
37 ?callable $onRejected = null
38 ): PromiseInterface {
39 if ($this->state === self::PENDING) {
40 $p = new Promise(null, [$this, 'cancel']);
41 $this->handlers[] = [$p, $onFulfilled, $onRejected];
42 $p->waitList = $this->waitList;
43 $p->waitList[] = $this;
44
45 return $p;
46 }
47
48 // Return a fulfilled promise and immediately invoke any callbacks.
49 if ($this->state === self::FULFILLED) {
50 $promise = Create::promiseFor($this->result);
51
52 return $onFulfilled ? $promise->then($onFulfilled) : $promise;
53 }
54
55 // It's either cancelled or rejected, so return a rejected promise
56 // and immediately invoke any callbacks.
57 $rejection = Create::rejectionFor($this->result);
58
59 return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
60 }
61
62 public function otherwise(callable $onRejected): PromiseInterface
63 {
64 return $this->then(null, $onRejected);
65 }
66
67 public function wait(bool $unwrap = true)
68 {
69 $this->waitIfPending();
70
71 if ($this->result instanceof PromiseInterface) {
72 return $this->result->wait($unwrap);
73 }
74 if ($unwrap) {
75 if ($this->state === self::FULFILLED) {
76 return $this->result;
77 }
78 // It's rejected so "unwrap" and throw an exception.
79 throw Create::exceptionFor($this->result);
80 }
81 }
82
83 public function getState(): string
84 {
85 return $this->state;
86 }
87
88 public function cancel(): void
89 {
90 if ($this->state !== self::PENDING) {
91 return;
92 }
93
94 $this->waitFn = $this->waitList = null;
95
96 if ($this->cancelFn) {
97 $fn = $this->cancelFn;
98 $this->cancelFn = null;
99 try {
100 $fn();
101 } catch (\Throwable $e) {
102 $this->reject($e);
103 }
104 }
105
106 // Reject the promise only if it wasn't rejected in a then callback.
107 /** @psalm-suppress RedundantCondition */
108 if ($this->state === self::PENDING) {
109 $this->reject(new CancellationException('Promise has been cancelled'));
110 }
111 }
112
113 public function resolve($value): void
114 {
115 $this->settle(self::FULFILLED, $value);
116 }
117
118 public function reject($reason): void
119 {
120 $this->settle(self::REJECTED, $reason);
121 }
122
123 private function settle(string $state, $value): void
124 {
125 if ($this->state !== self::PENDING) {
126 // Ignore calls with the same resolution.
127 if ($state === $this->state && $value === $this->result) {
128 return;
129 }
130 throw $this->state === $state
131 ? new \LogicException("The promise is already {$state}.")
132 : new \LogicException("Cannot change a {$this->state} promise to {$state}");
133 }
134
135 if ($value === $this) {
136 throw new \LogicException('Cannot fulfill or reject a promise with itself');
137 }
138
139 // Clear out the state of the promise but stash the handlers.
140 $this->state = $state;
141 $this->result = $value;
142 $handlers = $this->handlers;
143 $this->handlers = null;
144 $this->waitList = $this->waitFn = null;
145 $this->cancelFn = null;
146
147 if (!$handlers) {
148 return;
149 }
150
151 // If the value was not a settled promise or a thenable, then resolve
152 // it in the task queue using the correct ID.
153 if (!is_object($value) || !method_exists($value, 'then')) {
154 $id = $state === self::FULFILLED ? 1 : 2;
155 // It's a success, so resolve the handlers in the queue.
156 Utils::queue()->add(static function () use ($id, $value, $handlers): void {
157 foreach ($handlers as $handler) {
158 self::callHandler($id, $value, $handler);
159 }
160 });
161 } elseif ($value instanceof Promise && Is::pending($value)) {
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): void {
168 foreach ($handlers as $handler) {
169 self::callHandler(1, $value, $handler);
170 }
171 },
172 static function ($reason) use ($handlers): void {
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 private static function callHandler(int $index, $value, array $handler): void
189 {
190 /** @var PromiseInterface $promise */
191 $promise = $handler[0];
192
193 // The promise may have been cancelled or resolved before placing
194 // this thunk in the queue.
195 if (Is::settled($promise)) {
196 return;
197 }
198
199 try {
200 if (isset($handler[$index])) {
201 /*
202 * If $f throws an exception, then $handler will be in the exception
203 * stack trace. Since $handler contains a reference to the callable
204 * itself we get a circular reference. We clear the $handler
205 * here to avoid that memory leak.
206 */
207 $f = $handler[$index];
208 unset($handler);
209 $promise->resolve($f($value));
210 } elseif ($index === 1) {
211 // Forward resolution values as-is.
212 $promise->resolve($value);
213 } else {
214 // Forward rejections down the chain.
215 $promise->reject($value);
216 }
217 } catch (\Throwable $reason) {
218 $promise->reject($reason);
219 }
220 }
221
222 private function waitIfPending(): void
223 {
224 if ($this->state !== self::PENDING) {
225 return;
226 } elseif ($this->waitFn) {
227 $this->invokeWaitFn();
228 } elseif ($this->waitList) {
229 $this->invokeWaitList();
230 } else {
231 // If there's no wait function, then reject the promise.
232 $this->reject('Cannot wait on a promise that has '
233 .'no internal wait function. You must provide a wait '
234 .'function when constructing the promise to be able to '
235 .'wait on a promise.');
236 }
237
238 Utils::queue()->run();
239
240 /** @psalm-suppress RedundantCondition */
241 if ($this->state === self::PENDING) {
242 $this->reject('Invoking the wait callback did not resolve the promise');
243 }
244 }
245
246 private function invokeWaitFn(): void
247 {
248 try {
249 $wfn = $this->waitFn;
250 $this->waitFn = null;
251 $wfn(true);
252 } catch (\Throwable $reason) {
253 if ($this->state === self::PENDING) {
254 // The promise has not been resolved yet, so reject the promise
255 // with the exception.
256 $this->reject($reason);
257 } else {
258 // The promise was already resolved, so there's a problem in
259 // the application.
260 throw $reason;
261 }
262 }
263 }
264
265 private function invokeWaitList(): void
266 {
267 $waitList = $this->waitList;
268 $this->waitList = null;
269
270 foreach ($waitList as $result) {
271 do {
272 $result->waitIfPending();
273 $result = $result->result;
274 } while ($result instanceof Promise);
275
276 if ($result instanceof PromiseInterface) {
277 $result->wait(false);
278 }
279 }
280 }
281}
Note: See TracBrowser for help on using the repository browser.