dokuwiki-matrixnotifierwas-plugin – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | <?php |
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace GuzzleHttp\Promise; |
||
6 | |||
7 | /** |
||
8 | * Promises/A+ implementation that avoids recursion when possible. |
||
9 | * |
||
10 | * @see https://promisesaplus.com/ |
||
11 | * |
||
12 | * @final |
||
13 | */ |
||
14 | class 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 | } |