Skip to content

Instantly share code, notes, and snippets.

@BurlesonBrad
Created October 29, 2019 18:39
Show Gist options
  • Save BurlesonBrad/be992fb3453dd07a74e7ed96de056bf9 to your computer and use it in GitHub Desktop.
Save BurlesonBrad/be992fb3453dd07a74e7ed96de056bf9 to your computer and use it in GitHub Desktop.
/wp-content/plugins/google-site-kit/third-party/react/promise/src/
<?php
namespace Google\Site_Kit_Dependencies\React\Promise;
class Promise implements \Google\Site_Kit_Dependencies\React\Promise\ExtendedPromiseInterface, \Google\Site_Kit_Dependencies\React\Promise\CancellablePromiseInterface
{
private $canceller;
private $result;
private $handlers = [];
private $progressHandlers = [];
private $requiredCancelRequests = 0;
private $cancelRequests = 0;
public function __construct(callable $resolver, callable $canceller = null)
{
$this->canceller = $canceller;
// Explicitly overwrite arguments with null values before invoking
// resolver function. This ensure that these arguments do not show up
// in the stack trace in PHP 7+ only.
$cb = $resolver;
$resolver = $canceller = null;
$this->call($cb);
}
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->then($onFulfilled, $onRejected, $onProgress);
}
if (null === $this->canceller) {
return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
}
// This promise has a canceller, so we create a new child promise which
// has a canceller that invokes the parent canceller if all other
// followers are also cancelled. We keep a reference to this promise
// instance for the static canceller function and clear this to avoid
// keeping a cyclic reference between parent and follower.
$parent = $this;
++$parent->requiredCancelRequests;
return new static($this->resolver($onFulfilled, $onRejected, $onProgress), static function () use(&$parent) {
if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
$parent->cancel();
}
$parent = null;
});
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->done($onFulfilled, $onRejected, $onProgress);
}
$this->handlers[] = static function (\Google\Site_Kit_Dependencies\React\Promise\ExtendedPromiseInterface $promise) use($onFulfilled, $onRejected) {
$promise->done($onFulfilled, $onRejected);
};
if ($onProgress) {
$this->progressHandlers[] = $onProgress;
}
}
public function otherwise(callable $onRejected)
{
return $this->then(null, static function ($reason) use($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
return new \Google\Site_Kit_Dependencies\React\Promise\RejectedPromise($reason);
}
return $onRejected($reason);
});
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(static function ($value) use($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use($value) {
return $value;
});
}, static function ($reason) use($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use($reason) {
return new \Google\Site_Kit_Dependencies\React\Promise\RejectedPromise($reason);
});
});
}
public function progress(callable $onProgress)
{
return $this->then(null, null, $onProgress);
}
public function cancel()
{
if (null === $this->canceller || null !== $this->result) {
return;
}
$canceller = $this->canceller;
$this->canceller = null;
$this->call($canceller);
}
private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return function ($resolve, $reject, $notify) use($onFulfilled, $onRejected, $onProgress) {
if ($onProgress) {
$progressHandler = static function ($update) use($notify, $onProgress) {
try {
$notify($onProgress($update));
} catch (\Throwable $e) {
$notify($e);
} catch (\Exception $e) {
$notify($e);
}
};
} else {
$progressHandler = $notify;
}
$this->handlers[] = static function (\Google\Site_Kit_Dependencies\React\Promise\ExtendedPromiseInterface $promise) use($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
$promise->then($onFulfilled, $onRejected)->done($resolve, $reject, $progressHandler);
};
$this->progressHandlers[] = $progressHandler;
};
}
private function reject($reason = null)
{
if (null !== $this->result) {
return;
}
$this->settle(reject($reason));
}
private function settle(\Google\Site_Kit_Dependencies\React\Promise\ExtendedPromiseInterface $promise)
{
$promise = $this->unwrap($promise);
if ($promise === $this) {
$promise = new \Google\Site_Kit_Dependencies\React\Promise\RejectedPromise(new \LogicException('Cannot resolve a promise with itself.'));
}
$handlers = $this->handlers;
$this->progressHandlers = $this->handlers = [];
$this->result = $promise;
$this->canceller = null;
foreach ($handlers as $handler) {
$handler($promise);
}
}
private function unwrap($promise)
{
$promise = $this->extract($promise);
while ($promise instanceof self && null !== $promise->result) {
$promise = $this->extract($promise->result);
}
return $promise;
}
private function extract($promise)
{
if ($promise instanceof \Google\Site_Kit_Dependencies\React\Promise\LazyPromise) {
$promise = $promise->promise();
}
return $promise;
}
private function call(callable $cb)
{
// Explicitly overwrite argument with null value. This ensure that this
// argument does not show up in the stack trace in PHP 7+ only.
$callback = $cb;
$cb = null;
// Use reflection to inspect number of arguments expected by this callback.
// We did some careful benchmarking here: Using reflection to avoid unneeded
// function arguments is actually faster than blindly passing them.
// Also, this helps avoiding unnecessary function arguments in the call stack
// if the callback creates an Exception (creating garbage cycles).
if (\is_array($callback)) {
$ref = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$ref = new \ReflectionMethod($callback, '__invoke');
} else {
$ref = new \ReflectionFunction($callback);
}
$args = $ref->getNumberOfParameters();
try {
if ($args === 0) {
$callback();
} else {
// Keep references to this promise instance for the static resolve/reject functions.
// By using static callbacks that are not bound to this instance
// and passing the target promise instance by reference, we can
// still execute its resolving logic and still clear this
// reference when settling the promise. This helps avoiding
// garbage cycles if any callback creates an Exception.
// These assumptions are covered by the test suite, so if you ever feel like
// refactoring this, go ahead, any alternative suggestions are welcome!
$target =& $this;
$progressHandlers =& $this->progressHandlers;
$callback(static function ($value = null) use(&$target) {
if ($target !== null) {
$target->settle(resolve($value));
$target = null;
}
}, static function ($reason = null) use(&$target) {
if ($target !== null) {
$target->reject($reason);
$target = null;
}
}, static function ($update = null) use(&$progressHandlers) {
foreach ($progressHandlers as $handler) {
$handler($update);
}
});
}
} catch (\Throwable $e) {
$target = null;
$this->reject($e);
} catch (\Exception $e) {
$target = null;
$this->reject($e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment