JezK
Edit File: SyncPromise.php
<?php declare(strict_types=1); namespace Automattic\WooCommerce\Vendor\GraphQL\Executor\Promise\Adapter; use Automattic\WooCommerce\Vendor\GraphQL\Error\InvariantViolation; /** * Synchronous promise implementation following Promises A+ spec. * * Uses a hybrid approach for optimal memory and performance: * - Lightweight closures in queue (fast execution) * - Heavy payload (callbacks) stored on promise objects and cleared after use * * Library users should use @see \Automattic\WooCommerce\Vendor\GraphQL\Deferred to create promises. * * @phpstan-type Executor callable(): mixed */ class SyncPromise { /** * TODO remove in next major version. * * @deprecated Use SyncPromiseQueue::run() instead */ public static function runQueue(): void { SyncPromiseQueue::run(); } /** * TODO remove in next major version. * * @deprecated Use SyncPromiseQueue methods instead * * @return \SplQueue<callable(): void> */ public static function getQueue(): \SplQueue { return SyncPromiseQueue::queue(); } public const PENDING = 0; public const FULFILLED = 1; public const REJECTED = 2; /** * Current promise state. * * @var 0|1|2 */ public int $state = self::PENDING; /** * Resolved value or rejection reason. * * @var mixed */ public $result; /** * Promises created in `then` method awaiting resolution. * * @var array< * int, * array{ * self, * (callable(mixed): mixed)|null, * (callable(\Throwable): mixed)|null, * }, * > */ protected array $waiting = []; /** * @param mixed $value * * @throws \Exception */ public function resolve($value): self { switch ($this->state) { case self::PENDING: if ($value === $this) { throw new \Exception('Cannot resolve promise with self.'); } if (is_object($value) && method_exists($value, 'then')) { $value->then( function ($resolvedValue): void { $this->resolve($resolvedValue); }, function (\Throwable $reason): void { $this->reject($reason); } ); return $this; } $this->state = self::FULFILLED; $this->result = $value; $this->enqueueWaitingPromises(); break; case self::FULFILLED: if ($this->result !== $value) { throw new \Exception('Cannot change value of fulfilled promise.'); } break; case self::REJECTED: throw new \Exception('Cannot resolve rejected promise.'); } return $this; } /** * @throws \Exception * * @return $this */ public function reject(\Throwable $reason): self { switch ($this->state) { case self::PENDING: $this->state = self::REJECTED; $this->result = $reason; $this->enqueueWaitingPromises(); break; case self::REJECTED: if ($reason !== $this->result) { throw new \Exception('Cannot change rejection reason.'); } break; case self::FULFILLED: throw new \Exception('Cannot reject fulfilled promise.'); } return $this; } /** * @param (callable(mixed): mixed)|null $onFulfilled * @param (callable(\Throwable): mixed)|null $onRejected * * @throws InvariantViolation */ public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self { if ($this->state === self::REJECTED && $onRejected === null ) { return $this; } if ($this->state === self::FULFILLED && $onFulfilled === null ) { return $this; } $child = new self(); $this->waiting[] = [$child, $onFulfilled, $onRejected]; if ($this->state !== self::PENDING) { $this->enqueueWaitingPromises(); } return $child; } /** @throws InvariantViolation */ private function enqueueWaitingPromises(): void { if ($this->state === self::PENDING) { throw new InvariantViolation('Cannot enqueue derived promises when parent is still pending.'); } $waiting = $this->waiting; if ($waiting === []) { return; } $this->waiting = []; $result = $this->result; SyncPromiseQueue::enqueue(static function () use ($waiting, $result): void { foreach ($waiting as [$child, $onFulfilled, $onRejected]) { try { if ($result instanceof \Throwable) { if ($onRejected === null) { $child->reject($result); } else { $child->resolve($onRejected($result)); } } else { $child->resolve( $onFulfilled === null ? $result : $onFulfilled($result) ); } } catch (\Throwable $e) { $child->reject($e); } } }); } }