PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / libraries / react / Promise / Promise.php

Promise.php in DecaLog 4.4.0, at includes/libraries/react/Promise/Promise.php

257 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace React\Promise;
4
5 class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
6 {
7 private $canceller;
8 private $result;
9
10 private $handlers = [];
11 private $progressHandlers = [];
12
13 private $requiredCancelRequests = 0;
14 private $cancelRequests = 0;
15
16 public function __construct(callable $resolver, callable $canceller = null)
17 {
18 $this->canceller = $canceller;
19
20 // Explicitly overwrite arguments with null values before invoking
21 // resolver function. This ensure that these arguments do not show up
22 // in the stack trace in PHP 7+ only.
23 $cb = $resolver;
24 $resolver = $canceller = null;
25 $this->call($cb);
26 }
27
28 public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
29 {
30 if (null !== $this->result) {
31 return $this->result->then($onFulfilled, $onRejected, $onProgress);
32 }
33
34 if (null === $this->canceller) {
35 return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
36 }
37
38 // This promise has a canceller, so we create a new child promise which
39 // has a canceller that invokes the parent canceller if all other
40 // followers are also cancelled. We keep a reference to this promise
41 // instance for the static canceller function and clear this to avoid
42 // keeping a cyclic reference between parent and follower.
43 $parent = $this;
44 ++$parent->requiredCancelRequests;
45
46 return new static(
47 $this->resolver($onFulfilled, $onRejected, $onProgress),
48 static function () use (&$parent) {
49 if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
50 $parent->cancel();
51 }
52
53 $parent = null;
54 }
55 );
56 }
57
58 public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
59 {
60 if (null !== $this->result) {
61 return $this->result->done($onFulfilled, $onRejected, $onProgress);
62 }
63
64 $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
65 $promise
66 ->done($onFulfilled, $onRejected);
67 };
68
69 if ($onProgress) {
70 $this->progressHandlers[] = $onProgress;
71 }
72 }
73
74 public function otherwise(callable $onRejected)
75 {
76 return $this->then(null, static function ($reason) use ($onRejected) {
77 if (!_checkTypehint($onRejected, $reason)) {
78 return new RejectedPromise($reason);
79 }
80
81 return $onRejected($reason);
82 });
83 }
84
85 public function always(callable $onFulfilledOrRejected)
86 {
87 return $this->then(static function ($value) use ($onFulfilledOrRejected) {
88 return resolve($onFulfilledOrRejected())->then(function () use ($value) {
89 return $value;
90 });
91 }, static function ($reason) use ($onFulfilledOrRejected) {
92 return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
93 return new RejectedPromise($reason);
94 });
95 });
96 }
97
98 public function progress(callable $onProgress)
99 {
100 return $this->then(null, null, $onProgress);
101 }
102
103 public function cancel()
104 {
105 if (null === $this->canceller || null !== $this->result) {
106 return;
107 }
108
109 $canceller = $this->canceller;
110 $this->canceller = null;
111
112 $this->call($canceller);
113 }
114
115 private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
116 {
117 return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
118 if ($onProgress) {
119 $progressHandler = static function ($update) use ($notify, $onProgress) {
120 try {
121 $notify($onProgress($update));
122 } catch (\Throwable $e) {
123 $notify($e);
124 } catch (\Exception $e) {
125 $notify($e);
126 }
127 };
128 } else {
129 $progressHandler = $notify;
130 }
131
132 $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
133 $promise
134 ->then($onFulfilled, $onRejected)
135 ->done($resolve, $reject, $progressHandler);
136 };
137
138 $this->progressHandlers[] = $progressHandler;
139 };
140 }
141
142 private function reject($reason = null)
143 {
144 if (null !== $this->result) {
145 return;
146 }
147
148 $this->settle(reject($reason));
149 }
150
151 private function settle(ExtendedPromiseInterface $promise)
152 {
153 $promise = $this->unwrap($promise);
154
155 if ($promise === $this) {
156 $promise = new RejectedPromise(
157 new \LogicException('Cannot resolve a promise with itself.')
158 );
159 }
160
161 $handlers = $this->handlers;
162
163 $this->progressHandlers = $this->handlers = [];
164 $this->result = $promise;
165 $this->canceller = null;
166
167 foreach ($handlers as $handler) {
168 $handler($promise);
169 }
170 }
171
172 private function unwrap($promise)
173 {
174 $promise = $this->extract($promise);
175
176 while ($promise instanceof self && null !== $promise->result) {
177 $promise = $this->extract($promise->result);
178 }
179
180 return $promise;
181 }
182
183 private function extract($promise)
184 {
185 if ($promise instanceof LazyPromise) {
186 $promise = $promise->promise();
187 }
188
189 return $promise;
190 }
191
192 private function call(callable $cb)
193 {
194 // Explicitly overwrite argument with null value. This ensure that this
195 // argument does not show up in the stack trace in PHP 7+ only.
196 $callback = $cb;
197 $cb = null;
198
199 // Use reflection to inspect number of arguments expected by this callback.
200 // We did some careful benchmarking here: Using reflection to avoid unneeded
201 // function arguments is actually faster than blindly passing them.
202 // Also, this helps avoiding unnecessary function arguments in the call stack
203 // if the callback creates an Exception (creating garbage cycles).
204 if (\is_array($callback)) {
205 $ref = new \ReflectionMethod($callback[0], $callback[1]);
206 } elseif (\is_object($callback) && !$callback instanceof \Closure) {
207 $ref = new \ReflectionMethod($callback, '__invoke');
208 } else {
209 $ref = new \ReflectionFunction($callback);
210 }
211 $args = $ref->getNumberOfParameters();
212
213 try {
214 if ($args === 0) {
215 $callback();
216 } else {
217 // Keep references to this promise instance for the static resolve/reject functions.
218 // By using static callbacks that are not bound to this instance
219 // and passing the target promise instance by reference, we can
220 // still execute its resolving logic and still clear this
221 // reference when settling the promise. This helps avoiding
222 // garbage cycles if any callback creates an Exception.
223 // These assumptions are covered by the test suite, so if you ever feel like
224 // refactoring this, go ahead, any alternative suggestions are welcome!
225 $target =& $this;
226 $progressHandlers =& $this->progressHandlers;
227
228 $callback(
229 static function ($value = null) use (&$target) {
230 if ($target !== null) {
231 $target->settle(resolve($value));
232 $target = null;
233 }
234 },
235 static function ($reason = null) use (&$target) {
236 if ($target !== null) {
237 $target->reject($reason);
238 $target = null;
239 }
240 },
241 static function ($update = null) use (&$progressHandlers) {
242 foreach ($progressHandlers as $handler) {
243 $handler($update);
244 }
245 }
246 );
247 }
248 } catch (\Throwable $e) {
249 $target = null;
250 $this->reject($e);
251 } catch (\Exception $e) {
252 $target = null;
253 $this->reject($e);
254 }
255 }
256 }
257