friendship ended with social-app. php is my new best friend
1<?php 2 3namespace React\Promise; 4 5use React\Promise\Exception\CompositeException; 6use React\Promise\Internal\FulfilledPromise; 7use React\Promise\Internal\RejectedPromise; 8 9/** 10 * Creates a promise for the supplied `$promiseOrValue`. 11 * 12 * If `$promiseOrValue` is a value, it will be the resolution value of the 13 * returned promise. 14 * 15 * If `$promiseOrValue` is a thenable (any object that provides a `then()` method), 16 * a trusted promise that follows the state of the thenable is returned. 17 * 18 * If `$promiseOrValue` is a promise, it will be returned as is. 19 * 20 * @template T 21 * @param PromiseInterface<T>|T $promiseOrValue 22 * @return PromiseInterface<T> 23 */ 24function resolve($promiseOrValue): PromiseInterface 25{ 26 if ($promiseOrValue instanceof PromiseInterface) { 27 return $promiseOrValue; 28 } 29 30 if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) { 31 $canceller = null; 32 33 if (\method_exists($promiseOrValue, 'cancel')) { 34 $canceller = [$promiseOrValue, 'cancel']; 35 assert(\is_callable($canceller)); 36 } 37 38 /** @var Promise<T> */ 39 return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void { 40 $promiseOrValue->then($resolve, $reject); 41 }, $canceller); 42 } 43 44 return new FulfilledPromise($promiseOrValue); 45} 46 47/** 48 * Creates a rejected promise for the supplied `$reason`. 49 * 50 * If `$reason` is a value, it will be the rejection value of the 51 * returned promise. 52 * 53 * If `$reason` is a promise, its completion value will be the rejected 54 * value of the returned promise. 55 * 56 * This can be useful in situations where you need to reject a promise without 57 * throwing an exception. For example, it allows you to propagate a rejection with 58 * the value of another promise. 59 * 60 * @return PromiseInterface<never> 61 */ 62function reject(\Throwable $reason): PromiseInterface 63{ 64 return new RejectedPromise($reason); 65} 66 67/** 68 * Returns a promise that will resolve only once all the items in 69 * `$promisesOrValues` have resolved. The resolution value of the returned promise 70 * will be an array containing the resolution values of each of the items in 71 * `$promisesOrValues`. 72 * 73 * @template T 74 * @param iterable<PromiseInterface<T>|T> $promisesOrValues 75 * @return PromiseInterface<array<T>> 76 */ 77function all(iterable $promisesOrValues): PromiseInterface 78{ 79 $cancellationQueue = new Internal\CancellationQueue(); 80 81 /** @var Promise<array<T>> */ 82 return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { 83 $toResolve = 0; 84 /** @var bool */ 85 $continue = true; 86 $values = []; 87 88 foreach ($promisesOrValues as $i => $promiseOrValue) { 89 $cancellationQueue->enqueue($promiseOrValue); 90 $values[$i] = null; 91 ++$toResolve; 92 93 resolve($promiseOrValue)->then( 94 function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void { 95 $values[$i] = $value; 96 97 if (0 === --$toResolve && !$continue) { 98 $resolve($values); 99 } 100 }, 101 function (\Throwable $reason) use (&$continue, $reject): void { 102 $continue = false; 103 $reject($reason); 104 } 105 ); 106 107 if (!$continue && !\is_array($promisesOrValues)) { 108 break; 109 } 110 } 111 112 $continue = false; 113 if ($toResolve === 0) { 114 $resolve($values); 115 } 116 }, $cancellationQueue); 117} 118 119/** 120 * Initiates a competitive race that allows one winner. Returns a promise which is 121 * resolved in the same way the first settled promise resolves. 122 * 123 * The returned promise will become **infinitely pending** if `$promisesOrValues` 124 * contains 0 items. 125 * 126 * @template T 127 * @param iterable<PromiseInterface<T>|T> $promisesOrValues 128 * @return PromiseInterface<T> 129 */ 130function race(iterable $promisesOrValues): PromiseInterface 131{ 132 $cancellationQueue = new Internal\CancellationQueue(); 133 134 /** @var Promise<T> */ 135 return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { 136 $continue = true; 137 138 foreach ($promisesOrValues as $promiseOrValue) { 139 $cancellationQueue->enqueue($promiseOrValue); 140 141 resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void { 142 $continue = false; 143 }); 144 145 if (!$continue && !\is_array($promisesOrValues)) { 146 break; 147 } 148 } 149 }, $cancellationQueue); 150} 151 152/** 153 * Returns a promise that will resolve when any one of the items in 154 * `$promisesOrValues` resolves. The resolution value of the returned promise 155 * will be the resolution value of the triggering item. 156 * 157 * The returned promise will only reject if *all* items in `$promisesOrValues` are 158 * rejected. The rejection value will be an array of all rejection reasons. 159 * 160 * The returned promise will also reject with a `React\Promise\Exception\LengthException` 161 * if `$promisesOrValues` contains 0 items. 162 * 163 * @template T 164 * @param iterable<PromiseInterface<T>|T> $promisesOrValues 165 * @return PromiseInterface<T> 166 */ 167function any(iterable $promisesOrValues): PromiseInterface 168{ 169 $cancellationQueue = new Internal\CancellationQueue(); 170 171 /** @var Promise<T> */ 172 return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { 173 $toReject = 0; 174 $continue = true; 175 $reasons = []; 176 177 foreach ($promisesOrValues as $i => $promiseOrValue) { 178 $cancellationQueue->enqueue($promiseOrValue); 179 ++$toReject; 180 181 resolve($promiseOrValue)->then( 182 function ($value) use ($resolve, &$continue): void { 183 $continue = false; 184 $resolve($value); 185 }, 186 function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void { 187 $reasons[$i] = $reason; 188 189 if (0 === --$toReject && !$continue) { 190 $reject(new CompositeException( 191 $reasons, 192 'All promises rejected.' 193 )); 194 } 195 } 196 ); 197 198 if (!$continue && !\is_array($promisesOrValues)) { 199 break; 200 } 201 } 202 203 $continue = false; 204 if ($toReject === 0 && !$reasons) { 205 $reject(new Exception\LengthException( 206 'Must contain at least 1 item but contains only 0 items.' 207 )); 208 } elseif ($toReject === 0) { 209 $reject(new CompositeException( 210 $reasons, 211 'All promises rejected.' 212 )); 213 } 214 }, $cancellationQueue); 215} 216 217/** 218 * Sets the global rejection handler for unhandled promise rejections. 219 * 220 * Note that rejected promises should always be handled similar to how any 221 * exceptions should always be caught in a `try` + `catch` block. If you remove 222 * the last reference to a rejected promise that has not been handled, it will 223 * report an unhandled promise rejection. See also the [`reject()` function](#reject) 224 * for more details. 225 * 226 * The `?callable $callback` argument MUST be a valid callback function that 227 * accepts a single `Throwable` argument or a `null` value to restore the 228 * default promise rejection handler. The return value of the callback function 229 * will be ignored and has no effect, so you SHOULD return a `void` value. The 230 * callback function MUST NOT throw or the program will be terminated with a 231 * fatal error. 232 * 233 * The function returns the previous rejection handler or `null` if using the 234 * default promise rejection handler. 235 * 236 * The default promise rejection handler will log an error message plus its 237 * stack trace: 238 * 239 * ```php 240 * // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 241 * React\Promise\reject(new RuntimeException('Unhandled')); 242 * ``` 243 * 244 * The promise rejection handler may be used to use customize the log message or 245 * write to custom log targets. As a rule of thumb, this function should only be 246 * used as a last resort and promise rejections are best handled with either the 247 * [`then()` method](#promiseinterfacethen), the 248 * [`catch()` method](#promiseinterfacecatch), or the 249 * [`finally()` method](#promiseinterfacefinally). 250 * See also the [`reject()` function](#reject) for more details. 251 * 252 * @param callable(\Throwable):void|null $callback 253 * @return callable(\Throwable):void|null 254 */ 255function set_rejection_handler(?callable $callback): ?callable 256{ 257 static $current = null; 258 $previous = $current; 259 $current = $callback; 260 261 return $previous; 262} 263 264/** 265 * @internal 266 */ 267function _checkTypehint(callable $callback, \Throwable $reason): bool 268{ 269 if (\is_array($callback)) { 270 $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); 271 } elseif (\is_object($callback) && !$callback instanceof \Closure) { 272 $callbackReflection = new \ReflectionMethod($callback, '__invoke'); 273 } else { 274 assert($callback instanceof \Closure || \is_string($callback)); 275 $callbackReflection = new \ReflectionFunction($callback); 276 } 277 278 $parameters = $callbackReflection->getParameters(); 279 280 if (!isset($parameters[0])) { 281 return true; 282 } 283 284 $expectedException = $parameters[0]; 285 286 // Extract the type of the argument and handle different possibilities 287 $type = $expectedException->getType(); 288 289 $isTypeUnion = true; 290 $types = []; 291 292 switch (true) { 293 case $type === null: 294 break; 295 case $type instanceof \ReflectionNamedType: 296 $types = [$type]; 297 break; 298 case $type instanceof \ReflectionIntersectionType: 299 $isTypeUnion = false; 300 case $type instanceof \ReflectionUnionType: 301 $types = $type->getTypes(); 302 break; 303 default: 304 throw new \LogicException('Unexpected return value of ReflectionParameter::getType'); 305 } 306 307 // If there is no type restriction, it matches 308 if (empty($types)) { 309 return true; 310 } 311 312 foreach ($types as $type) { 313 314 if ($type instanceof \ReflectionIntersectionType) { 315 foreach ($type->getTypes() as $typeToMatch) { 316 assert($typeToMatch instanceof \ReflectionNamedType); 317 $name = $typeToMatch->getName(); 318 if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) { 319 break; 320 } 321 } 322 assert(isset($matches)); 323 } else { 324 assert($type instanceof \ReflectionNamedType); 325 $name = $type->getName(); 326 $matches = !$type->isBuiltin() && $reason instanceof $name; 327 } 328 329 // If we look for a single match (union), we can return early on match 330 // If we look for a full match (intersection), we can return early on mismatch 331 if ($matches) { 332 if ($isTypeUnion) { 333 return true; 334 } 335 } else { 336 if (!$isTypeUnion) { 337 return false; 338 } 339 } 340 } 341 342 // If we look for a single match (union) and did not return early, we matched no type and are false 343 // If we look for a full match (intersection) and did not return early, we matched all types and are true 344 return $isTypeUnion ? false : true; 345}