/htdocs/includes/sabre/sabre/event/lib/Promise.php

https://github.com/hregis/dolibarr · PHP · 320 lines · 107 code · 41 blank · 172 comment · 16 complexity · 8bf600e1f01fac95c0e8cdfe74cbbcce MD5 · raw file

  1. <?php
  2. namespace Sabre\Event;
  3. use Exception;
  4. /**
  5. * An implementation of the Promise pattern.
  6. *
  7. * A promise represents the result of an asynchronous operation.
  8. * At any given point a promise can be in one of three states:
  9. *
  10. * 1. Pending (the promise does not have a result yet).
  11. * 2. Fulfilled (the asynchronous operation has completed with a result).
  12. * 3. Rejected (the asynchronous operation has completed with an error).
  13. *
  14. * To get a callback when the operation has finished, use the `then` method.
  15. *
  16. * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
  17. * @author Evert Pot (http://evertpot.com/)
  18. * @license http://sabre.io/license/ Modified BSD License
  19. */
  20. class Promise {
  21. /**
  22. * The asynchronous operation is pending.
  23. */
  24. const PENDING = 0;
  25. /**
  26. * The asynchronous operation has completed, and has a result.
  27. */
  28. const FULFILLED = 1;
  29. /**
  30. * The asynchronous operation has completed with an error.
  31. */
  32. const REJECTED = 2;
  33. /**
  34. * The current state of this promise.
  35. *
  36. * @var int
  37. */
  38. public $state = self::PENDING;
  39. /**
  40. * Creates the promise.
  41. *
  42. * The passed argument is the executor. The executor is automatically
  43. * called with two arguments.
  44. *
  45. * Each are callbacks that map to $this->fulfill and $this->reject.
  46. * Using the executor is optional.
  47. *
  48. * @param callable $executor
  49. */
  50. function __construct(callable $executor = null) {
  51. if ($executor) {
  52. $executor(
  53. [$this, 'fulfill'],
  54. [$this, 'reject']
  55. );
  56. }
  57. }
  58. /**
  59. * This method allows you to specify the callback that will be called after
  60. * the promise has been fulfilled or rejected.
  61. *
  62. * Both arguments are optional.
  63. *
  64. * This method returns a new promise, which can be used for chaining.
  65. * If either the onFulfilled or onRejected callback is called, you may
  66. * return a result from this callback.
  67. *
  68. * If the result of this callback is yet another promise, the result of
  69. * _that_ promise will be used to set the result of the returned promise.
  70. *
  71. * If either of the callbacks return any other value, the returned promise
  72. * is automatically fulfilled with that value.
  73. *
  74. * If either of the callbacks throw an exception, the returned promise will
  75. * be rejected and the exception will be passed back.
  76. *
  77. * @param callable $onFulfilled
  78. * @param callable $onRejected
  79. * @return Promise
  80. */
  81. function then(callable $onFulfilled = null, callable $onRejected = null) {
  82. // This new subPromise will be returned from this function, and will
  83. // be fulfilled with the result of the onFulfilled or onRejected event
  84. // handlers.
  85. $subPromise = new self();
  86. switch ($this->state) {
  87. case self::PENDING :
  88. // The operation is pending, so we keep a reference to the
  89. // event handlers so we can call them later.
  90. $this->subscribers[] = [$subPromise, $onFulfilled, $onRejected];
  91. break;
  92. case self::FULFILLED :
  93. // The async operation is already fulfilled, so we trigger the
  94. // onFulfilled callback asap.
  95. $this->invokeCallback($subPromise, $onFulfilled);
  96. break;
  97. case self::REJECTED :
  98. // The async operation failed, so we call teh onRejected
  99. // callback asap.
  100. $this->invokeCallback($subPromise, $onRejected);
  101. break;
  102. }
  103. return $subPromise;
  104. }
  105. /**
  106. * Add a callback for when this promise is rejected.
  107. *
  108. * Its usage is identical to then(). However, the otherwise() function is
  109. * preferred.
  110. *
  111. * @param callable $onRejected
  112. * @return Promise
  113. */
  114. function otherwise(callable $onRejected) {
  115. return $this->then(null, $onRejected);
  116. }
  117. /**
  118. * Marks this promise as fulfilled and sets its return value.
  119. *
  120. * @param mixed $value
  121. * @return void
  122. */
  123. function fulfill($value = null) {
  124. if ($this->state !== self::PENDING) {
  125. throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
  126. }
  127. $this->state = self::FULFILLED;
  128. $this->value = $value;
  129. foreach ($this->subscribers as $subscriber) {
  130. $this->invokeCallback($subscriber[0], $subscriber[1]);
  131. }
  132. }
  133. /**
  134. * Marks this promise as rejected, and set it's rejection reason.
  135. *
  136. * While it's possible to use any PHP value as the reason, it's highly
  137. * recommended to use an Exception for this.
  138. *
  139. * @param mixed $reason
  140. * @return void
  141. */
  142. function reject($reason = null) {
  143. if ($this->state !== self::PENDING) {
  144. throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
  145. }
  146. $this->state = self::REJECTED;
  147. $this->value = $reason;
  148. foreach ($this->subscribers as $subscriber) {
  149. $this->invokeCallback($subscriber[0], $subscriber[2]);
  150. }
  151. }
  152. /**
  153. * Stops execution until this promise is resolved.
  154. *
  155. * This method stops exection completely. If the promise is successful with
  156. * a value, this method will return this value. If the promise was
  157. * rejected, this method will throw an exception.
  158. *
  159. * This effectively turns the asynchronous operation into a synchronous
  160. * one. In PHP it might be useful to call this on the last promise in a
  161. * chain.
  162. *
  163. * @throws Exception
  164. * @return mixed
  165. */
  166. function wait() {
  167. $hasEvents = true;
  168. while ($this->state === self::PENDING) {
  169. if (!$hasEvents) {
  170. throw new \LogicException('There were no more events in the loop. This promise will never be fulfilled.');
  171. }
  172. // As long as the promise is not fulfilled, we tell the event loop
  173. // to handle events, and to block.
  174. $hasEvents = Loop\tick(true);
  175. }
  176. if ($this->state === self::FULFILLED) {
  177. // If the state of this promise is fulfilled, we can return the value.
  178. return $this->value;
  179. } else {
  180. // If we got here, it means that the asynchronous operation
  181. // errored. Therefore we need to throw an exception.
  182. $reason = $this->value;
  183. if ($reason instanceof Exception) {
  184. throw $reason;
  185. } elseif (is_scalar($reason)) {
  186. throw new Exception($reason);
  187. } else {
  188. $type = is_object($reason) ? get_class($reason) : gettype($reason);
  189. throw new Exception('Promise was rejected with reason of type: ' . $type);
  190. }
  191. }
  192. }
  193. /**
  194. * A list of subscribers. Subscribers are the callbacks that want us to let
  195. * them know if the callback was fulfilled or rejected.
  196. *
  197. * @var array
  198. */
  199. protected $subscribers = [];
  200. /**
  201. * The result of the promise.
  202. *
  203. * If the promise was fulfilled, this will be the result value. If the
  204. * promise was rejected, this property hold the rejection reason.
  205. *
  206. * @var mixed
  207. */
  208. protected $value = null;
  209. /**
  210. * This method is used to call either an onFulfilled or onRejected callback.
  211. *
  212. * This method makes sure that the result of these callbacks are handled
  213. * correctly, and any chained promises are also correctly fulfilled or
  214. * rejected.
  215. *
  216. * @param Promise $subPromise
  217. * @param callable $callBack
  218. * @return void
  219. */
  220. private function invokeCallback(Promise $subPromise, callable $callBack = null) {
  221. // We use 'nextTick' to ensure that the event handlers are always
  222. // triggered outside of the calling stack in which they were originally
  223. // passed to 'then'.
  224. //
  225. // This makes the order of execution more predictable.
  226. Loop\nextTick(function() use ($callBack, $subPromise) {
  227. if (is_callable($callBack)) {
  228. try {
  229. $result = $callBack($this->value);
  230. if ($result instanceof self) {
  231. // If the callback (onRejected or onFulfilled)
  232. // returned a promise, we only fulfill or reject the
  233. // chained promise once that promise has also been
  234. // resolved.
  235. $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']);
  236. } else {
  237. // If the callback returned any other value, we
  238. // immediately fulfill the chained promise.
  239. $subPromise->fulfill($result);
  240. }
  241. } catch (Exception $e) {
  242. // If the event handler threw an exception, we need to make sure that
  243. // the chained promise is rejected as well.
  244. $subPromise->reject($e);
  245. }
  246. } else {
  247. if ($this->state === self::FULFILLED) {
  248. $subPromise->fulfill($this->value);
  249. } else {
  250. $subPromise->reject($this->value);
  251. }
  252. }
  253. });
  254. }
  255. /**
  256. * Alias for 'otherwise'.
  257. *
  258. * This function is now deprecated and will be removed in a future version.
  259. *
  260. * @param callable $onRejected
  261. * @deprecated
  262. * @return Promise
  263. */
  264. function error(callable $onRejected) {
  265. return $this->otherwise($onRejected);
  266. }
  267. /**
  268. * Deprecated.
  269. *
  270. * Please use Sabre\Event\Promise::all
  271. *
  272. * @param Promise[] $promises
  273. * @deprecated
  274. * @return Promise
  275. */
  276. static function all(array $promises) {
  277. return Promise\all($promises);
  278. }
  279. }