AmpFutureAdapter.php
2 months ago
AmpPromiseAdapter.php
2 months ago
ReactPromiseAdapter.php
2 months ago
SyncPromise.php
2 months ago
SyncPromiseAdapter.php
2 months ago
SyncPromiseQueue.php
2 months ago
SyncPromiseAdapter.php
165 lines
| 1 | <?php declare(strict_types=1); |
| 2 | |
| 3 | namespace Automattic\WooCommerce\Vendor\GraphQL\Executor\Promise\Adapter; |
| 4 | |
| 5 | use Automattic\WooCommerce\Vendor\GraphQL\Deferred; |
| 6 | use Automattic\WooCommerce\Vendor\GraphQL\Error\InvariantViolation; |
| 7 | use Automattic\WooCommerce\Vendor\GraphQL\Executor\Promise\Promise; |
| 8 | use Automattic\WooCommerce\Vendor\GraphQL\Executor\Promise\PromiseAdapter; |
| 9 | use Automattic\WooCommerce\Vendor\GraphQL\Utils\Utils; |
| 10 | |
| 11 | /** |
| 12 | * Allows changing order of field resolution even in sync environments |
| 13 | * (by leveraging queue of deferreds and promises). |
| 14 | */ |
| 15 | class SyncPromiseAdapter implements PromiseAdapter |
| 16 | { |
| 17 | public function isThenable($value): bool |
| 18 | { |
| 19 | return $value instanceof SyncPromise; |
| 20 | } |
| 21 | |
| 22 | /** @throws InvariantViolation */ |
| 23 | public function convertThenable($thenable): Promise |
| 24 | { |
| 25 | if (! $thenable instanceof SyncPromise) { |
| 26 | // End-users should always use Deferred, not SyncPromise directly |
| 27 | $deferredClass = Deferred::class; |
| 28 | $safeThenable = Utils::printSafe($thenable); |
| 29 | throw new InvariantViolation("Expected instance of {$deferredClass}, got {$safeThenable}."); |
| 30 | } |
| 31 | |
| 32 | return new Promise($thenable, $this); |
| 33 | } |
| 34 | |
| 35 | /** @throws InvariantViolation */ |
| 36 | public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise |
| 37 | { |
| 38 | $syncPromise = $promise->adoptedPromise; |
| 39 | assert($syncPromise instanceof SyncPromise); |
| 40 | |
| 41 | return new Promise($syncPromise->then($onFulfilled, $onRejected), $this); |
| 42 | } |
| 43 | |
| 44 | /** |
| 45 | * @throws \Exception |
| 46 | * @throws InvariantViolation |
| 47 | */ |
| 48 | public function create(callable $resolver): Promise |
| 49 | { |
| 50 | $syncPromise = new SyncPromise(); |
| 51 | |
| 52 | try { |
| 53 | $resolver( |
| 54 | [$syncPromise, 'resolve'], |
| 55 | [$syncPromise, 'reject'] |
| 56 | ); |
| 57 | } catch (\Throwable $e) { |
| 58 | $syncPromise->reject($e); |
| 59 | } |
| 60 | |
| 61 | return new Promise($syncPromise, $this); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * @throws \Exception |
| 66 | * @throws InvariantViolation |
| 67 | */ |
| 68 | public function createFulfilled($value = null): Promise |
| 69 | { |
| 70 | $syncPromise = new SyncPromise(); |
| 71 | |
| 72 | return new Promise($syncPromise->resolve($value), $this); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * @throws \Exception |
| 77 | * @throws InvariantViolation |
| 78 | */ |
| 79 | public function createRejected(\Throwable $reason): Promise |
| 80 | { |
| 81 | $syncPromise = new SyncPromise(); |
| 82 | |
| 83 | return new Promise($syncPromise->reject($reason), $this); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * @throws \Exception |
| 88 | * @throws InvariantViolation |
| 89 | */ |
| 90 | public function all(iterable $promisesOrValues): Promise |
| 91 | { |
| 92 | $all = new SyncPromise(); |
| 93 | |
| 94 | $total = is_array($promisesOrValues) |
| 95 | ? count($promisesOrValues) |
| 96 | : iterator_count($promisesOrValues); |
| 97 | $count = 0; |
| 98 | $result = []; |
| 99 | |
| 100 | $resolveAllWhenFinished = function () use (&$count, &$total, $all, &$result): void { |
| 101 | if ($count === $total) { |
| 102 | $all->resolve($result); |
| 103 | } |
| 104 | }; |
| 105 | |
| 106 | foreach ($promisesOrValues as $index => $promiseOrValue) { |
| 107 | if ($promiseOrValue instanceof Promise) { |
| 108 | $result[$index] = null; |
| 109 | $promiseOrValue->then( |
| 110 | static function ($value) use (&$result, $index, &$count, &$resolveAllWhenFinished): void { |
| 111 | $result[$index] = $value; |
| 112 | ++$count; |
| 113 | $resolveAllWhenFinished(); |
| 114 | }, |
| 115 | [$all, 'reject'] |
| 116 | ); |
| 117 | continue; |
| 118 | } |
| 119 | |
| 120 | $result[$index] = $promiseOrValue; |
| 121 | ++$count; |
| 122 | } |
| 123 | |
| 124 | $resolveAllWhenFinished(); |
| 125 | |
| 126 | return new Promise($all, $this); |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * Synchronously wait when promise completes. |
| 131 | * |
| 132 | * @throws InvariantViolation |
| 133 | * |
| 134 | * @return mixed |
| 135 | */ |
| 136 | public function wait(Promise $promise) |
| 137 | { |
| 138 | $this->beforeWait($promise); |
| 139 | |
| 140 | $syncPromise = $promise->adoptedPromise; |
| 141 | assert($syncPromise instanceof SyncPromise); |
| 142 | |
| 143 | while ($syncPromise->state === SyncPromise::PENDING) { |
| 144 | SyncPromiseQueue::run(); |
| 145 | $this->onWait($promise); |
| 146 | } |
| 147 | |
| 148 | if ($syncPromise->state === SyncPromise::FULFILLED) { |
| 149 | return $syncPromise->result; |
| 150 | } |
| 151 | |
| 152 | if ($syncPromise->state === SyncPromise::REJECTED) { |
| 153 | throw $syncPromise->result; |
| 154 | } |
| 155 | |
| 156 | throw new InvariantViolation('Could not resolve promise.'); |
| 157 | } |
| 158 | |
| 159 | /** Execute just before starting to run promise completion. */ |
| 160 | protected function beforeWait(Promise $promise): void {} |
| 161 | |
| 162 | /** Execute while running promise completion. */ |
| 163 | protected function onWait(Promise $promise): void {} |
| 164 | } |
| 165 |