Skip to content

Add react/promise v2 to v3 transition helper #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
}
],
"autoload": {
"files": [
"src/Discord/PromiseHelpers/bootstrap.php"
],
"psr-4": {
"Discord\\Http\\": "src/Discord",
"Tests\\Discord\\Http\\": "tests/Discord"
Expand All @@ -19,7 +22,7 @@
"php": "^7.4|^8.0",
"react/http": "^1.2",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"react/promise": "^2.2"
"react/promise": "^2.8 || >=3.0 <=3.1"
},
"suggest": {
"guzzlehttp/guzzle": "For alternative to ReactPHP/Http Browser"
Expand Down
6 changes: 3 additions & 3 deletions src/Discord/DriverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
namespace Discord\Http;

use Psr\Http\Message\ResponseInterface;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;

/**
* Interface for an HTTP driver.
Expand All @@ -28,7 +28,7 @@ interface DriverInterface
*
* @param Request $request
*
* @return ExtendedPromiseInterface<ResponseInterface>
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill<ResponseInterface>|\React\Promise\ExtendedPromiseInterface<ResponseInterface>
*/
public function runRequest(Request $request): ExtendedPromiseInterface;
public function runRequest(Request $request): PromiseInterface;
}
6 changes: 3 additions & 3 deletions src/Discord/Drivers/Guzzle.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
namespace Discord\Http\Drivers;

use Discord\Http\DriverInterface;
use Discord\Http\PromiseHelpers\Deferred;
use Discord\Http\Request;
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;

/**
* guzzlehttp/guzzle driver for Discord HTTP client. (still with React Promise).
Expand Down Expand Up @@ -55,7 +55,7 @@ public function __construct(?LoopInterface $loop = null, array $options = [])
$this->client = new Client($options);
}

public function runRequest(Request $request): ExtendedPromiseInterface
public function runRequest(Request $request): PromiseInterface
{
// Create a React promise
$deferred = new Deferred();
Expand Down
9 changes: 5 additions & 4 deletions src/Discord/Drivers/React.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
namespace Discord\Http\Drivers;

use Discord\Http\DriverInterface;
use Discord\Http\PromiseHelpers\PromiseInterfacePolyFill;
use Discord\Http\Request;
use React\EventLoop\LoopInterface;
use React\Http\Browser;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;
use React\Socket\Connector;

/**
Expand Down Expand Up @@ -54,12 +55,12 @@ public function __construct(LoopInterface $loop, array $options = [])
$this->browser = $browser->withRejectErrorResponse(false);
}

public function runRequest(Request $request): ExtendedPromiseInterface
public function runRequest(Request $request): PromiseInterface
{
return $this->browser->{$request->getMethod()}(
return new PromiseInterfacePolyFill($this->browser->{$request->getMethod()}(
$request->getUrl(),
$request->getHeaders(),
$request->getContent()
);
));
}
}
40 changes: 20 additions & 20 deletions src/Discord/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
use Discord\Http\Exceptions\NotFoundException;
use Discord\Http\Exceptions\RateLimitException;
use Discord\Http\Exceptions\RequestFailedException;
use Discord\Http\PromiseHelpers\Deferred;
use Discord\Http\Multipart\MultipartBody;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use React\Promise\ExtendedPromiseInterface;
use React\Promise\PromiseInterface;
use SplQueue;

/**
Expand All @@ -39,7 +39,7 @@ class Http
*
* @var string
*/
public const VERSION = 'v10.3.0';
public const VERSION = 'v10.4.0';

/**
* Current Discord HTTP API version.
Expand Down Expand Up @@ -160,9 +160,9 @@ public function setDriver(DriverInterface $driver): void
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
public function get($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function get($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -178,9 +178,9 @@ public function get($url, $content = null, array $headers = []): ExtendedPromise
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
public function post($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function post($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -196,9 +196,9 @@ public function post($url, $content = null, array $headers = []): ExtendedPromis
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
public function put($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function put($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -214,9 +214,9 @@ public function put($url, $content = null, array $headers = []): ExtendedPromise
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
public function patch($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function patch($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -232,9 +232,9 @@ public function patch($url, $content = null, array $headers = []): ExtendedPromi
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
public function delete($url, $content = null, array $headers = []): ExtendedPromiseInterface
public function delete($url, $content = null, array $headers = []): PromiseInterface
{
if (! ($url instanceof Endpoint)) {
$url = Endpoint::bind($url);
Expand All @@ -251,9 +251,9 @@ public function delete($url, $content = null, array $headers = []): ExtendedProm
* @param mixed $content
* @param array $headers
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
public function queueRequest(string $method, Endpoint $url, $content, array $headers = []): ExtendedPromiseInterface
public function queueRequest(string $method, Endpoint $url, $content, array $headers = []): PromiseInterface
{
$deferred = new Deferred();

Expand Down Expand Up @@ -318,16 +318,16 @@ protected function guessContent(&$content)
* @param Request $request
* @param Deferred $deferred
*
* @return ExtendedPromiseInterface
* @return \Discord\Http\PromiseHelpers\PromiseInterfacePolyFill|\React\Promise\ExtendedPromiseInterface
*/
protected function executeRequest(Request $request, Deferred $deferred = null): ExtendedPromiseInterface
protected function executeRequest(Request $request, Deferred $deferred = null): PromiseInterface
{
if ($deferred === null) {
$deferred = new Deferred();
}

if ($this->rateLimit) {
$deferred->reject($this->rateLimit);
$deferred->reject($this->rateLimit); // TODO handle resolve ratelimit

return $deferred->promise();
}
Expand Down Expand Up @@ -383,7 +383,7 @@ protected function executeRequest(Request $request, Deferred $deferred = null):
});
}

$deferred->reject($rateLimit->isGlobal() ? $this->rateLimit : $rateLimit);
$deferred->reject($rateLimit->isGlobal() ? $this->rateLimit : $rateLimit); // TODO handle resolve ratelimit
}
// Bad Gateway
// Cloudflare SSL Handshake error
Expand Down Expand Up @@ -476,7 +476,7 @@ protected function checkQueue(): void
--$this->waiting;
$this->checkQueue();
$deferred->resolve($result);
}, function ($e) use ($deferred) {
}, function ($e) use ($deferred) { // TODO handle resolve reject
--$this->waiting;
$this->checkQueue();
$deferred->reject($e);
Expand Down
26 changes: 26 additions & 0 deletions src/Discord/PromiseHelpers/CancellablePromiseInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/*
* This file is a part of the DiscordPHP-Http project.
*
* Copyright (c) 2021-present David Cole <[email protected]>
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE file.
*/

namespace Discord\Http\PromiseHelpers;

/**
* A transition helper from react/promise v2 to react/promise v3.
* Please do not use this polyfill class in place of real CancellablePromiseInterface.
*
* @see \React\Promise\CancellablePromiseInterface
*
* @internal Used internally for DiscordPHP v10
*
* @since 10.4.0
*/
interface CancellablePromiseInterface
{
public function cancel();
}
140 changes: 140 additions & 0 deletions src/Discord/PromiseHelpers/Deferred.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

/*
* This file is a part of the DiscordPHP-Http project.
*
* Copyright (c) 2021-present David Cole <[email protected]>
*
* This file is subject to the MIT license that is bundled
* with this source code in the LICENSE file.
*/

namespace Discord\Http\PromiseHelpers;

use React\Promise\Deferred as ReactDeferred;
use React\Promise\PromiseInterface;

/**
* A transition helper from react/promise v2 to react/promise v3.
* Please do not use this polyfill class in place of real Deferred.
*
* @see \React\Promise\Deferred
* @see PromisorInterface
*
* @internal Used internally for DiscordPHP v10
*
* @since 10.4.0
*/
final class Deferred implements PromisorInterface
{
/**
* The actual Promisor
*/
public ReactDeferred $deferred;

/**
* Determine the installed package is Promise-v3
*/
public static bool $isPromiseV3 = false;

/**
* @var PromiseInterfacePolyFill|PromiseInterfacePolyFill
*/
private $promise;

/**
* @param callable|ReactDeferred $canceller Canceller callback or a Deferred to use
*
* @throws \InvalidArgumentException $canceller is not null or callable or a Deferred
*/
public function __construct($canceller = null)
{
if ($canceller instanceof ReactDeferred) {
$this->deferred = $canceller;
} elseif (null === $canceller || is_callable($canceller)) {
$this->deferred = new ReactDeferred($canceller);
} else {
throw new \InvalidArgumentException('$canceller must be either null or callable or Deferred');
}
}

/**
* @return PromiseInterfacePolyFill|PromiseInterface|\React\Promise\ExtendedPromiseInterface
*/
public function promise()
{
if (!static::$isPromiseV3) {
// Just use the same react/promise v2 promise
return $this->deferred->promise();
}

if (null === $this->promise) {
// Wrap with the polyfill if user installed react/promise v3
$this->promise = new PromiseInterfacePolyFill($this->deferred->promise());
}

return $this->promise;
}

/**
* @see React\Promise\Deferred::resolve()
*
* @return void
*/
public function resolve($value = null)
{
$this->deferred->resolve($value);
}

/**
* @see React\Promise\Deferred::reject()
*
* @param \Throwable $reason required in Promise-v3, will be resolved if not a throwable
*
* @throws \InvalidArgumentException $reason is null & react/promise is v3
*
* @return void
*/
public function reject($reason = null)
{
if (static::$isPromiseV3) {
if (null === $reason) {
$reason = new \InvalidArgumentException('reject($reason) must not be null');
} elseif (!($reason instanceof \Throwable)) {
return $this->deferred->resolve($reason);
}
}

$this->deferred->reject($reason);
}

/**
* Not supported
*
* @deprecated
*/
public function notify($update = null)
{
if (method_exists($this->deferred, 'notify')) {
$this->deferred->notify($update);
return;
}

throw new \BadMethodCallException('notify() is not supported with this polyfill and react/promise v3');
}

/**
* Not supported
*
* @deprecated
*/
public function progress($update = null)
{
if (method_exists($this->deferred, 'progress')) {
$this->deferred->progress($update);
return;
}

throw new \BadMethodCallException('progress() is not supported with this polyfill and react/promise v3');
}
}
Loading