Promise.php 8.6 KB

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