friendship ended with social-app. php is my new best friend
1<?php 2 3namespace React\Promise; 4 5use React\Promise\Internal\RejectedPromise; 6 7/** 8 * @template T 9 * @template-implements PromiseInterface<T> 10 */ 11final class Promise implements PromiseInterface 12{ 13 /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */ 14 private $canceller; 15 16 /** @var ?PromiseInterface<T> */ 17 private $result; 18 19 /** @var list<callable(PromiseInterface<T>):void> */ 20 private $handlers = []; 21 22 /** @var int */ 23 private $requiredCancelRequests = 0; 24 25 /** @var bool */ 26 private $cancelled = false; 27 28 /** 29 * @param callable(callable(T):void,callable(\Throwable):void):void $resolver 30 * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller 31 */ 32 public function __construct(callable $resolver, ?callable $canceller = null) 33 { 34 $this->canceller = $canceller; 35 36 // Explicitly overwrite arguments with null values before invoking 37 // resolver function. This ensure that these arguments do not show up 38 // in the stack trace in PHP 7+ only. 39 $cb = $resolver; 40 $resolver = $canceller = null; 41 $this->call($cb); 42 } 43 44 public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface 45 { 46 if (null !== $this->result) { 47 return $this->result->then($onFulfilled, $onRejected); 48 } 49 50 if (null === $this->canceller) { 51 return new static($this->resolver($onFulfilled, $onRejected)); 52 } 53 54 // This promise has a canceller, so we create a new child promise which 55 // has a canceller that invokes the parent canceller if all other 56 // followers are also cancelled. We keep a reference to this promise 57 // instance for the static canceller function and clear this to avoid 58 // keeping a cyclic reference between parent and follower. 59 $parent = $this; 60 ++$parent->requiredCancelRequests; 61 62 return new static( 63 $this->resolver($onFulfilled, $onRejected), 64 static function () use (&$parent): void { 65 assert($parent instanceof self); 66 --$parent->requiredCancelRequests; 67 68 if ($parent->requiredCancelRequests <= 0) { 69 $parent->cancel(); 70 } 71 72 $parent = null; 73 } 74 ); 75 } 76 77 /** 78 * @template TThrowable of \Throwable 79 * @template TRejected 80 * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected 81 * @return PromiseInterface<T|TRejected> 82 */ 83 public function catch(callable $onRejected): PromiseInterface 84 { 85 return $this->then(null, static function (\Throwable $reason) use ($onRejected) { 86 if (!_checkTypehint($onRejected, $reason)) { 87 return new RejectedPromise($reason); 88 } 89 90 /** 91 * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected 92 */ 93 return $onRejected($reason); 94 }); 95 } 96 97 public function finally(callable $onFulfilledOrRejected): PromiseInterface 98 { 99 return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface { 100 /** @var T $value */ 101 return resolve($onFulfilledOrRejected())->then(function () use ($value) { 102 return $value; 103 }); 104 }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { 105 return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise { 106 return new RejectedPromise($reason); 107 }); 108 }); 109 } 110 111 public function cancel(): void 112 { 113 $this->cancelled = true; 114 $canceller = $this->canceller; 115 $this->canceller = null; 116 117 $parentCanceller = null; 118 119 if (null !== $this->result) { 120 // Forward cancellation to rejected promise to avoid reporting unhandled rejection 121 if ($this->result instanceof RejectedPromise) { 122 $this->result->cancel(); 123 } 124 125 // Go up the promise chain and reach the top most promise which is 126 // itself not following another promise 127 $root = $this->unwrap($this->result); 128 129 // Return if the root promise is already resolved or a 130 // FulfilledPromise or RejectedPromise 131 if (!$root instanceof self || null !== $root->result) { 132 return; 133 } 134 135 $root->requiredCancelRequests--; 136 137 if ($root->requiredCancelRequests <= 0) { 138 $parentCanceller = [$root, 'cancel']; 139 } 140 } 141 142 if (null !== $canceller) { 143 $this->call($canceller); 144 } 145 146 // For BC, we call the parent canceller after our own canceller 147 if ($parentCanceller) { 148 $parentCanceller(); 149 } 150 } 151 152 /** 153 * @deprecated 3.0.0 Use `catch()` instead 154 * @see self::catch() 155 */ 156 public function otherwise(callable $onRejected): PromiseInterface 157 { 158 return $this->catch($onRejected); 159 } 160 161 /** 162 * @deprecated 3.0.0 Use `finally()` instead 163 * @see self::finally() 164 */ 165 public function always(callable $onFulfilledOrRejected): PromiseInterface 166 { 167 return $this->finally($onFulfilledOrRejected); 168 } 169 170 private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable 171 { 172 return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void { 173 $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void { 174 $promise = $promise->then($onFulfilled, $onRejected); 175 176 if ($promise instanceof self && $promise->result === null) { 177 $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void { 178 $promise->then($resolve, $reject); 179 }; 180 } else { 181 $promise->then($resolve, $reject); 182 } 183 }; 184 }; 185 } 186 187 private function reject(\Throwable $reason): void 188 { 189 if (null !== $this->result) { 190 return; 191 } 192 193 $this->settle(reject($reason)); 194 } 195 196 /** 197 * @param PromiseInterface<T> $result 198 */ 199 private function settle(PromiseInterface $result): void 200 { 201 $result = $this->unwrap($result); 202 203 if ($result === $this) { 204 $result = new RejectedPromise( 205 new \LogicException('Cannot resolve a promise with itself.') 206 ); 207 } 208 209 if ($result instanceof self) { 210 $result->requiredCancelRequests++; 211 } else { 212 // Unset canceller only when not following a pending promise 213 $this->canceller = null; 214 } 215 216 $handlers = $this->handlers; 217 218 $this->handlers = []; 219 $this->result = $result; 220 221 foreach ($handlers as $handler) { 222 $handler($result); 223 } 224 225 // Forward cancellation to rejected promise to avoid reporting unhandled rejection 226 if ($this->cancelled && $result instanceof RejectedPromise) { 227 $result->cancel(); 228 } 229 } 230 231 /** 232 * @param PromiseInterface<T> $promise 233 * @return PromiseInterface<T> 234 */ 235 private function unwrap(PromiseInterface $promise): PromiseInterface 236 { 237 while ($promise instanceof self && null !== $promise->result) { 238 /** @var PromiseInterface<T> $promise */ 239 $promise = $promise->result; 240 } 241 242 return $promise; 243 } 244 245 /** 246 * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb 247 */ 248 private function call(callable $cb): void 249 { 250 // Explicitly overwrite argument with null value. This ensure that this 251 // argument does not show up in the stack trace in PHP 7+ only. 252 $callback = $cb; 253 $cb = null; 254 255 // Use reflection to inspect number of arguments expected by this callback. 256 // We did some careful benchmarking here: Using reflection to avoid unneeded 257 // function arguments is actually faster than blindly passing them. 258 // Also, this helps avoiding unnecessary function arguments in the call stack 259 // if the callback creates an Exception (creating garbage cycles). 260 if (\is_array($callback)) { 261 $ref = new \ReflectionMethod($callback[0], $callback[1]); 262 } elseif (\is_object($callback) && !$callback instanceof \Closure) { 263 $ref = new \ReflectionMethod($callback, '__invoke'); 264 } else { 265 assert($callback instanceof \Closure || \is_string($callback)); 266 $ref = new \ReflectionFunction($callback); 267 } 268 $args = $ref->getNumberOfParameters(); 269 270 try { 271 if ($args === 0) { 272 $callback(); 273 } else { 274 // Keep references to this promise instance for the static resolve/reject functions. 275 // By using static callbacks that are not bound to this instance 276 // and passing the target promise instance by reference, we can 277 // still execute its resolving logic and still clear this 278 // reference when settling the promise. This helps avoiding 279 // garbage cycles if any callback creates an Exception. 280 // These assumptions are covered by the test suite, so if you ever feel like 281 // refactoring this, go ahead, any alternative suggestions are welcome! 282 $target =& $this; 283 284 $callback( 285 static function ($value) use (&$target): void { 286 if ($target !== null) { 287 $target->settle(resolve($value)); 288 $target = null; 289 } 290 }, 291 static function (\Throwable $reason) use (&$target): void { 292 if ($target !== null) { 293 $target->reject($reason); 294 $target = null; 295 } 296 } 297 ); 298 } 299 } catch (\Throwable $e) { 300 $target = null; 301 $this->reject($e); 302 } 303 } 304}