1
0
Fork 0
mirror of synced 2024-07-04 22:20:45 +12:00

Generify promises

This commit is contained in:
Jake Barnby 2022-10-12 13:55:43 +13:00
parent 20bee94175
commit ee2e8fcc23
No known key found for this signature in database
GPG key ID: C437A8CC85B96E9C
5 changed files with 232 additions and 225 deletions

View file

@ -0,0 +1,88 @@
<?php
namespace Appwrite\GraphQL\Promises;
use Appwrite\Promises\Promise;
use GraphQL\Executor\Promise\Promise as GQLPromise;
use GraphQL\Executor\Promise\PromiseAdapter;
abstract class Adapter implements PromiseAdapter
{
/**
* Returns true if the given value is a {@see Promise}.
*
* @param $value
* @return bool
*/
public function isThenable($value): bool
{
return $value instanceof Promise;
}
/**
* Converts a {@see Promise} into a {@see GQLPromise}
*
* @param mixed $thenable
* @return GQLPromise
* @throws \Exception
*/
public function convertThenable(mixed $thenable): GQLPromise
{
if (!$thenable instanceof Promise) {
throw new \Exception('Expected instance of Promise got: ' . \gettype($thenable));
}
return new GQLPromise($thenable, $this);
}
/**
* Returns a promise that resolves when the passed in promise resolves.
*
* @param GQLPromise $promise
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @return GQLPromise
*/
public function then(
GQLPromise $promise,
?callable $onFulfilled = null,
?callable $onRejected = null
): GQLPromise {
/** @var Promise $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
return new GQLPromise($adoptedPromise->then($onFulfilled, $onRejected), $this);
}
/**
* Create a new promise with the given resolver function.
*
* @param callable $resolver
* @return GQLPromise
*/
abstract public function create(callable $resolver): GQLPromise;
/**
* Create a new promise that is fulfilled with the given value.
*
* @param mixed $value
* @return GQLPromise
*/
abstract public function createFulfilled(mixed $value = null): GQLPromise;
/**
* Create a new promise that is rejected with the given reason.
*
* @param mixed $reason
* @return GQLPromise
*/
abstract public function createRejected(mixed $reason): GQLPromise;
/**
* Create a new promise that resolves when all passed in promises resolve.
*
* @param array $promisesOrValues
* @return GQLPromise
*/
abstract public function all(array $promisesOrValues): GQLPromise;
}

View file

@ -0,0 +1,41 @@
<?php
namespace Appwrite\GraphQL\Promises\Adapter;
use Appwrite\GraphQL\Promises\Adapter;
use GraphQL\Executor\Promise\Promise as GQLPromise;
class Swoole extends Adapter
{
public function create(callable $resolver): GQLPromise
{
$promise = new Swoole(function ($resolve, $reject) use ($resolver) {
$resolver($resolve, $reject);
});
return new GQLPromise($promise, $this);
}
public function createFulfilled($value = null): GQLPromise
{
$promise = new Swoole(function ($resolve, $reject) use ($value) {
$resolve($value);
});
return new GQLPromise($promise, $this);
}
public function createRejected($reason): GQLPromise
{
$promise = new Swoole(function ($resolve, $reject) use ($reason) {
$reject($reason);
});
return new GQLPromise($promise, $this);
}
public function all(array $promisesOrValues): GQLPromise
{
return new GQLPromise(Swoole::all($promisesOrValues), $this);
}
}

View file

@ -1,147 +0,0 @@
<?php
namespace Appwrite\GraphQL\Promises;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Utils\Utils;
use Swoole\Coroutine\Channel;
class CoroutinePromiseAdapter implements PromiseAdapter
{
/**
* Returns true if the given value is a {@see CoroutinePromise}.
*
* @param $value
* @return bool
*/
public function isThenable($value): bool
{
return $value instanceof CoroutinePromise;
}
/**
* Converts a {@see CoroutinePromise} into a {@see Promise}
*
* @param $thenable
* @return Promise
*/
public function convertThenable($thenable): Promise
{
if (!$thenable instanceof CoroutinePromise) {
throw new InvariantViolation('Expected instance of CoroutinePromise, got ' . Utils::printSafe($thenable));
}
return new Promise($thenable, $this);
}
/**
* Returns a promise that resolves when the passed in promise resolves.
*
* @param Promise $promise
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @return Promise
*/
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise
{
/** @var CoroutinePromise $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
}
/**
* Create a new promise with the given resolver function.
*
* @param callable $resolver
* @return Promise
*/
public function create(callable $resolver): Promise
{
$promise = new CoroutinePromise(function ($resolve, $reject) use ($resolver) {
$resolver($resolve, $reject);
});
return new Promise($promise, $this);
}
/**
* Create a new promise that is fulfilled with the given value.
*
* @param $value
* @return Promise
*/
public function createFulfilled($value = null): Promise
{
$promise = new CoroutinePromise(function ($resolve, $reject) use ($value) {
$resolve($value);
});
return new Promise($promise, $this);
}
/**
* Create a new promise that is rejected with the given reason.
*
* @param $reason
* @return Promise
*/
public function createRejected($reason): Promise
{
$promise = new CoroutinePromise(function ($resolve, $reject) use ($reason) {
$reject($reason);
});
return new Promise($promise, $this);
}
/**
* Create a new promise that resolves when all passed in promises resolve.
*
* @param array $promisesOrValues
* @return Promise
*/
public function all(array $promisesOrValues): Promise
{
$all = new CoroutinePromise(function (callable $resolve, callable $reject) use ($promisesOrValues) {
$ticks = count($promisesOrValues);
$firstError = null;
$channel = new Channel($ticks);
$result = [];
$key = 0;
foreach ($promisesOrValues as $promiseOrValue) {
if (!$promiseOrValue instanceof Promise) {
$result[$key] = $promiseOrValue;
$channel->push(true);
}
$promiseOrValue->then(function ($value) use ($key, &$result, $channel) {
$result[$key] = $value;
$channel->push(true);
}, function ($error) use ($channel, &$firstError) {
$channel->push(true);
if ($firstError === null) {
$firstError = $error;
}
});
$key++;
}
while ($ticks--) {
$channel->pop();
}
$channel->close();
if ($firstError !== null) {
$reject($firstError);
return;
}
$resolve($result);
});
return new Promise($all, $this);
}
}

View file

@ -1,18 +1,8 @@
<?php
namespace Appwrite\GraphQL\Promises;
namespace Appwrite\Promises;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Utils\Utils;
use Swoole\Coroutine\Channel;
/**
* Inspired by https://github.com/streamcommon/promise/blob/master/lib/ExtSwoolePromise.php
*
* @package Appwrite\GraphQL
*/
class CoroutinePromise
abstract class Promise
{
protected const STATE_PENDING = 1;
protected const STATE_FULFILLED = 0;
@ -35,22 +25,22 @@ class CoroutinePromise
$this->setResult($value);
$this->setState(self::STATE_REJECTED);
};
\go(function () use ($executor, $resolve, $reject) {
try {
$executor($resolve, $reject);
} catch (\Throwable $exception) {
$reject($exception);
}
});
$this->execute($executor, $resolve, $reject);
}
abstract protected function execute(
callable $executor,
callable $resolve,
callable $reject
): void;
/**
* Create a new promise from the given callable.
*
* @param callable $promise
* @return CoroutinePromise
* @return self
*/
final public static function create(callable $promise): CoroutinePromise
public static function create(callable $promise): self
{
return new static($promise);
}
@ -59,9 +49,9 @@ class CoroutinePromise
* Resolve promise with given value.
*
* @param mixed $value
* @return CoroutinePromise
* @return self
*/
final public static function resolve(mixed $value): CoroutinePromise
public static function resolve(mixed $value): self
{
return new static(function (callable $resolve) use ($value) {
$resolve($value);
@ -72,9 +62,9 @@ class CoroutinePromise
* Rejects the promise with the given reason.
*
* @param mixed $value
* @return CoroutinePromise
* @return self
*/
final public static function reject(mixed $value): CoroutinePromise
public static function reject(mixed $value): self
{
return new static(function (callable $resolve, callable $reject) use ($value) {
$reject($value);
@ -85,9 +75,9 @@ class CoroutinePromise
* Catch any exception thrown by the executor.
*
* @param callable $onRejected
* @return CoroutinePromise
* @return self
*/
final public function catch(callable $onRejected): CoroutinePromise
public function catch(callable $onRejected): self
{
return $this->then(null, $onRejected);
}
@ -97,10 +87,12 @@ class CoroutinePromise
*
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @return CoroutinePromise
* @return self
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): CoroutinePromise
{
public function then(
?callable $onFulfilled = null,
?callable $onRejected = null
): self {
if ($this->isRejected() && $onRejected === null) {
return $this;
}
@ -127,49 +119,10 @@ class CoroutinePromise
/**
* Returns a promise that completes when all passed in promises complete.
*
* @param iterable|CoroutinePromise[] $promises
* @return CoroutinePromise
* @param iterable|self[] $promises
* @return self
*/
public static function all(iterable $promises): CoroutinePromise
{
return self::create(function (callable $resolve, callable $reject) use ($promises) {
$ticks = count($promises);
$firstError = null;
$channel = new Channel($ticks);
$result = [];
$key = 0;
foreach ($promises as $promise) {
if (!$promise instanceof CoroutinePromise) {
$channel->close();
throw new InvariantViolation('Expected instance of CoroutinePromise, got ' . Utils::printSafe($promise));
}
$promise->then(function ($value) use ($key, &$result, $channel) {
$result[$key] = $value;
$channel->push(true);
return $value;
}, function ($error) use ($channel, &$firstError) {
$channel->push(true);
if ($firstError === null) {
$firstError = $error;
}
});
$key++;
}
while ($ticks--) {
$channel->pop();
}
$channel->close();
if ($firstError !== null) {
$reject($firstError);
return;
}
$resolve($result);
});
}
abstract public static function all(iterable $promises): self;
/**
* Set resolved result
@ -177,18 +130,20 @@ class CoroutinePromise
* @param mixed $value
* @return void
*/
private function setResult(mixed $value): void
protected function setResult(mixed $value): void
{
if (!$value instanceof Promise) {
if (!$value instanceof self) {
$this->result = $value;
return;
}
$resolved = false;
$callable = function ($value) use (&$resolved) {
$this->setResult($value);
$resolved = true;
};
$value->then($callable, $callable);
while (!$resolved) {
@ -202,7 +157,7 @@ class CoroutinePromise
* @param integer $state
* @return void
*/
final protected function setState(int $state): void
protected function setState(int $state): void
{
$this->state = $state;
}
@ -212,7 +167,7 @@ class CoroutinePromise
*
* @return boolean
*/
final protected function isPending(): bool
protected function isPending(): bool
{
return $this->state == self::STATE_PENDING;
}
@ -222,7 +177,7 @@ class CoroutinePromise
*
* @return boolean
*/
final protected function isFulfilled(): bool
protected function isFulfilled(): bool
{
return $this->state == self::STATE_FULFILLED;
}
@ -232,7 +187,7 @@ class CoroutinePromise
*
* @return boolean
*/
final protected function isRejected(): bool
protected function isRejected(): bool
{
return $this->state == self::STATE_REJECTED;
}

View file

@ -0,0 +1,70 @@
<?php
namespace Appwrite\Promises;
use Swoole\Coroutine\Channel;
class Swoole extends Promise
{
public function __construct(?callable $executor = null)
{
parent::__construct($executor);
}
protected function execute(
callable $executor,
callable $resolve,
callable $reject
): void {
\go(function () use ($executor, $resolve, $reject) {
try {
$executor($resolve, $reject);
} catch (\Throwable $exception) {
$reject($exception);
}
});
}
/**
* Returns a promise that completes when all passed in promises complete.
*
* @param iterable|Swoole[] $promises
* @return Promise
*/
public static function all(iterable $promises): Promise
{
return self::create(function (callable $resolve, callable $reject) use ($promises) {
$ticks = count($promises);
$result = [];
$error = null;
$channel = new Channel($ticks);
$key = 0;
foreach ($promises as $promise) {
$promise->then(function ($value) use ($key, &$result, $channel) {
$result[$key] = $value;
$channel->push(true);
return $value;
}, function ($err) use ($channel, &$error) {
$channel->push(true);
if ($error === null) {
$error = $err;
}
});
$key++;
}
while ($ticks--) {
$channel->pop();
}
$channel->close();
if ($error !== null) {
$reject($error);
return;
}
$resolve($result);
});
}
}