PageRenderTime 29ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/guzzlehttp/promises/src/functions.php

https://gitlab.com/aleritty/yaitb
PHP | 495 lines | 224 code | 31 blank | 240 comment | 9 complexity | 1f71d89b2bbe5393396f1df5caf077b7 MD5 | raw file
  1. <?php
  2. namespace GuzzleHttp\Promise;
  3. /**
  4. * Get the global task queue used for promise resolution.
  5. *
  6. * This task queue MUST be run in an event loop in order for promises to be
  7. * settled asynchronously. It will be automatically run when synchronously
  8. * waiting on a promise.
  9. *
  10. * <code>
  11. * while ($eventLoop->isRunning()) {
  12. * GuzzleHttp\Promise\queue()->run();
  13. * }
  14. * </code>
  15. *
  16. * @return TaskQueue
  17. */
  18. function queue()
  19. {
  20. static $queue;
  21. if (!$queue) {
  22. $queue = new TaskQueue();
  23. }
  24. return $queue;
  25. }
  26. /**
  27. * Adds a function to run in the task queue when it is next `run()` and returns
  28. * a promise that is fulfilled or rejected with the result.
  29. *
  30. * @param callable $task Task function to run.
  31. *
  32. * @return PromiseInterface
  33. */
  34. function task(callable $task)
  35. {
  36. $queue = queue();
  37. $promise = new Promise([$queue, 'run']);
  38. $queue->add(function () use ($task, $promise) {
  39. try {
  40. $promise->resolve($task());
  41. } catch (\Exception $e) {
  42. $promise->reject($e);
  43. }
  44. });
  45. return $promise;
  46. }
  47. /**
  48. * Creates a promise for a value if the value is not a promise.
  49. *
  50. * @param mixed $value Promise or value.
  51. *
  52. * @return PromiseInterface
  53. */
  54. function promise_for($value)
  55. {
  56. if ($value instanceof PromiseInterface) {
  57. return $value;
  58. }
  59. // Return a Guzzle promise that shadows the given promise.
  60. if (method_exists($value, 'then')) {
  61. $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
  62. $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
  63. $promise = new Promise($wfn, $cfn);
  64. $value->then([$promise, 'resolve'], [$promise, 'reject']);
  65. return $promise;
  66. }
  67. return new FulfilledPromise($value);
  68. }
  69. /**
  70. * Creates a rejected promise for a reason if the reason is not a promise. If
  71. * the provided reason is a promise, then it is returned as-is.
  72. *
  73. * @param mixed $reason Promise or reason.
  74. *
  75. * @return PromiseInterface
  76. */
  77. function rejection_for($reason)
  78. {
  79. if ($reason instanceof PromiseInterface) {
  80. return $reason;
  81. }
  82. return new RejectedPromise($reason);
  83. }
  84. /**
  85. * Create an exception for a rejected promise value.
  86. *
  87. * @param mixed $reason
  88. *
  89. * @return \Exception
  90. */
  91. function exception_for($reason)
  92. {
  93. return $reason instanceof \Exception
  94. ? $reason
  95. : new RejectionException($reason);
  96. }
  97. /**
  98. * Returns an iterator for the given value.
  99. *
  100. * @param mixed $value
  101. *
  102. * @return \Iterator
  103. */
  104. function iter_for($value)
  105. {
  106. if ($value instanceof \Iterator) {
  107. return $value;
  108. } elseif (is_array($value)) {
  109. return new \ArrayIterator($value);
  110. } else {
  111. return new \ArrayIterator([$value]);
  112. }
  113. }
  114. /**
  115. * Synchronously waits on a promise to resolve and returns an inspection state
  116. * array.
  117. *
  118. * Returns a state associative array containing a "state" key mapping to a
  119. * valid promise state. If the state of the promise is "fulfilled", the array
  120. * will contain a "value" key mapping to the fulfilled value of the promise. If
  121. * the promise is rejected, the array will contain a "reason" key mapping to
  122. * the rejection reason of the promise.
  123. *
  124. * @param PromiseInterface $promise Promise or value.
  125. *
  126. * @return array
  127. */
  128. function inspect(PromiseInterface $promise)
  129. {
  130. try {
  131. return [
  132. 'state' => PromiseInterface::FULFILLED,
  133. 'value' => $promise->wait()
  134. ];
  135. } catch (RejectionException $e) {
  136. return ['state' => 'rejected', 'reason' => $e->getReason()];
  137. } catch (\Exception $e) {
  138. return ['state' => 'rejected', 'reason' => $e];
  139. }
  140. }
  141. /**
  142. * Waits on all of the provided promises, but does not unwrap rejected promises
  143. * as thrown exception.
  144. *
  145. * Returns an array of inspection state arrays.
  146. *
  147. * @param PromiseInterface[] $promises Traversable of promises to wait upon.
  148. *
  149. * @return array
  150. * @see GuzzleHttp\Promise\inspect for the inspection state array format.
  151. */
  152. function inspect_all($promises)
  153. {
  154. $results = [];
  155. foreach ($promises as $key => $promise) {
  156. $results[$key] = inspect($promise);
  157. }
  158. return $results;
  159. }
  160. /**
  161. * Waits on all of the provided promises and returns the fulfilled values.
  162. *
  163. * Returns an array that contains the value of each promise (in the same order
  164. * the promises were provided). An exception is thrown if any of the promises
  165. * are rejected.
  166. *
  167. * @param mixed $promises Iterable of PromiseInterface objects to wait on.
  168. *
  169. * @return array
  170. * @throws \Exception on error
  171. */
  172. function unwrap($promises)
  173. {
  174. $results = [];
  175. foreach ($promises as $key => $promise) {
  176. $results[$key] = $promise->wait();
  177. }
  178. return $results;
  179. }
  180. /**
  181. * Given an array of promises, return a promise that is fulfilled when all the
  182. * items in the array are fulfilled.
  183. *
  184. * The promise's fulfillment value is an array with fulfillment values at
  185. * respective positions to the original array. If any promise in the array
  186. * rejects, the returned promise is rejected with the rejection reason.
  187. *
  188. * @param mixed $promises Promises or values.
  189. *
  190. * @return Promise
  191. */
  192. function all($promises)
  193. {
  194. $results = [];
  195. return each(
  196. $promises,
  197. function ($value, $idx) use (&$results) {
  198. $results[$idx] = $value;
  199. },
  200. function ($reason, $idx, Promise $aggregate) {
  201. $aggregate->reject($reason);
  202. }
  203. )->then(function () use (&$results) {
  204. ksort($results);
  205. return $results;
  206. });
  207. }
  208. /**
  209. * Initiate a competitive race between multiple promises or values (values will
  210. * become immediately fulfilled promises).
  211. *
  212. * When count amount of promises have been fulfilled, the returned promise is
  213. * fulfilled with an array that contains the fulfillment values of the winners
  214. * in order of resolution.
  215. *
  216. * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
  217. * if the number of fulfilled promises is less than the desired $count.
  218. *
  219. * @param int $count Total number of promises.
  220. * @param mixed $promises Promises or values.
  221. *
  222. * @return Promise
  223. */
  224. function some($count, $promises)
  225. {
  226. $results = [];
  227. $rejections = [];
  228. return each(
  229. $promises,
  230. function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
  231. if ($p->getState() !== PromiseInterface::PENDING) {
  232. return;
  233. }
  234. $results[$idx] = $value;
  235. if (count($results) >= $count) {
  236. $p->resolve(null);
  237. }
  238. },
  239. function ($reason) use (&$rejections) {
  240. $rejections[] = $reason;
  241. }
  242. )->then(
  243. function () use (&$results, &$rejections, $count) {
  244. if (count($results) !== $count) {
  245. throw new AggregateException(
  246. 'Not enough promises to fulfill count',
  247. $rejections
  248. );
  249. }
  250. ksort($results);
  251. return array_values($results);
  252. }
  253. );
  254. }
  255. /**
  256. * Like some(), with 1 as count. However, if the promise fulfills, the
  257. * fulfillment value is not an array of 1 but the value directly.
  258. *
  259. * @param mixed $promises Promises or values.
  260. *
  261. * @return PromiseInterface
  262. */
  263. function any($promises)
  264. {
  265. return some(1, $promises)->then(function ($values) { return $values[0]; });
  266. }
  267. /**
  268. * Returns a promise that is fulfilled when all of the provided promises have
  269. * been fulfilled or rejected.
  270. *
  271. * The returned promise is fulfilled with an array of inspection state arrays.
  272. *
  273. * @param mixed $promises Promises or values.
  274. *
  275. * @return Promise
  276. * @see GuzzleHttp\Promise\inspect for the inspection state array format.
  277. */
  278. function settle($promises)
  279. {
  280. $results = [];
  281. return each(
  282. $promises,
  283. function ($value, $idx) use (&$results) {
  284. $results[$idx] = ['state' => 'fulfilled', 'value' => $value];
  285. },
  286. function ($reason, $idx) use (&$results) {
  287. $results[$idx] = ['state' => 'rejected', 'reason' => $reason];
  288. }
  289. )->then(function () use (&$results) {
  290. ksort($results);
  291. return $results;
  292. });
  293. }
  294. /**
  295. * Given an iterator that yields promises or values, returns a promise that is
  296. * fulfilled with a null value when the iterator has been consumed or the
  297. * aggregate promise has been fulfilled or rejected.
  298. *
  299. * $onFulfilled is a function that accepts the fulfilled value, iterator
  300. * index, and the aggregate promise. The callback can invoke any necessary side
  301. * effects and choose to resolve or reject the aggregate promise if needed.
  302. *
  303. * $onRejected is a function that accepts the rejection reason, iterator
  304. * index, and the aggregate promise. The callback can invoke any necessary side
  305. * effects and choose to resolve or reject the aggregate promise if needed.
  306. *
  307. * @param mixed $iterable Iterator or array to iterate over.
  308. * @param callable $onFulfilled
  309. * @param callable $onRejected
  310. *
  311. * @return Promise
  312. */
  313. function each(
  314. $iterable,
  315. callable $onFulfilled = null,
  316. callable $onRejected = null
  317. ) {
  318. return (new EachPromise($iterable, [
  319. 'fulfilled' => $onFulfilled,
  320. 'rejected' => $onRejected
  321. ]))->promise();
  322. }
  323. /**
  324. * Like each, but only allows a certain number of outstanding promises at any
  325. * given time.
  326. *
  327. * $concurrency may be an integer or a function that accepts the number of
  328. * pending promises and returns a numeric concurrency limit value to allow for
  329. * dynamic a concurrency size.
  330. *
  331. * @param mixed $iterable
  332. * @param int|callable $concurrency
  333. * @param callable $onFulfilled
  334. * @param callable $onRejected
  335. *
  336. * @return mixed
  337. */
  338. function each_limit(
  339. $iterable,
  340. $concurrency,
  341. callable $onFulfilled = null,
  342. callable $onRejected = null
  343. ) {
  344. return (new EachPromise($iterable, [
  345. 'fulfilled' => $onFulfilled,
  346. 'rejected' => $onRejected,
  347. 'concurrency' => $concurrency
  348. ]))->promise();
  349. }
  350. /**
  351. * Like each_limit, but ensures that no promise in the given $iterable argument
  352. * is rejected. If any promise is rejected, then the aggregate promise is
  353. * rejected with the encountered rejection.
  354. *
  355. * @param mixed $iterable
  356. * @param int|callable $concurrency
  357. * @param callable $onFulfilled
  358. *
  359. * @return mixed
  360. */
  361. function each_limit_all(
  362. $iterable,
  363. $concurrency,
  364. callable $onFulfilled = null
  365. ) {
  366. return each_limit(
  367. $iterable,
  368. $concurrency,
  369. $onFulfilled,
  370. function ($reason, $idx, PromiseInterface $aggregate) {
  371. $aggregate->reject($reason);
  372. }
  373. );
  374. }
  375. /**
  376. * Returns true if a promise is fulfilled.
  377. *
  378. * @param PromiseInterface $promise
  379. *
  380. * @return bool
  381. */
  382. function is_fulfilled(PromiseInterface $promise)
  383. {
  384. return $promise->getState() === PromiseInterface::FULFILLED;
  385. }
  386. /**
  387. * Returns true if a promise is rejected.
  388. *
  389. * @param PromiseInterface $promise
  390. *
  391. * @return bool
  392. */
  393. function is_rejected(PromiseInterface $promise)
  394. {
  395. return $promise->getState() === PromiseInterface::REJECTED;
  396. }
  397. /**
  398. * Returns true if a promise is fulfilled or rejected.
  399. *
  400. * @param PromiseInterface $promise
  401. *
  402. * @return bool
  403. */
  404. function is_settled(PromiseInterface $promise)
  405. {
  406. return $promise->getState() !== PromiseInterface::PENDING;
  407. }
  408. /**
  409. * Creates a promise that is resolved using a generator that yields values or
  410. * promises (somewhat similar to C#'s async keyword).
  411. *
  412. * When called, the coroutine function will start an instance of the generator
  413. * and returns a promise that is fulfilled with its final yielded value.
  414. *
  415. * Control is returned back to the generator when the yielded promise settles.
  416. * This can lead to less verbose code when doing lots of sequential async calls
  417. * with minimal processing in between.
  418. *
  419. * use GuzzleHttp\Promise;
  420. *
  421. * function createPromise($value) {
  422. * return new Promise\FulfilledPromise($value);
  423. * }
  424. *
  425. * $promise = Promise\coroutine(function () {
  426. * $value = (yield createPromise('a'));
  427. * try {
  428. * $value = (yield createPromise($value . 'b'));
  429. * } catch (\Exception $e) {
  430. * // The promise was rejected.
  431. * }
  432. * yield $value . 'c';
  433. * });
  434. *
  435. * // Outputs "abc"
  436. * $promise->then(function ($v) { echo $v; });
  437. *
  438. * @param callable $generatorFn Generator function to wrap into a promise.
  439. *
  440. * @return Promise
  441. * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
  442. */
  443. function coroutine(callable $generatorFn)
  444. {
  445. $generator = $generatorFn();
  446. return __next_coroutine($generator->current(), $generator)->then();
  447. }
  448. /** @internal */
  449. function __next_coroutine($yielded, \Generator $generator)
  450. {
  451. return promise_for($yielded)->then(
  452. function ($value) use ($generator) {
  453. $nextYield = $generator->send($value);
  454. return $generator->valid()
  455. ? __next_coroutine($nextYield, $generator)
  456. : $value;
  457. },
  458. function ($reason) use ($generator) {
  459. $nextYield = $generator->throw(exception_for($reason));
  460. // The throw was caught, so keep iterating on the coroutine
  461. return __next_coroutine($nextYield, $generator);
  462. }
  463. );
  464. }