Skip to content

Commit 175f973

Browse files
authored
Merge pull request #23 from php-http/feature/rewrite-promise
Rewrite promise
2 parents b83b4d1 + dca54bf commit 175f973

File tree

3 files changed

+137
-57
lines changed

3 files changed

+137
-57
lines changed

src/Client.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Psr\Http\Message\ResponseInterface;
1515
use Psr\Http\Message\StreamInterface;
1616
use React\EventLoop\LoopInterface;
17-
use React\Promise\Deferred;
1817
use React\HttpClient\Client as ReactClient;
1918
use React\HttpClient\Request as ReactRequest;
2019
use React\HttpClient\Response as ReactResponse;
@@ -93,45 +92,42 @@ public function sendRequest(RequestInterface $request)
9392
public function sendAsyncRequest(RequestInterface $request)
9493
{
9594
$reactRequest = $this->buildReactRequest($request);
96-
$deferred = new Deferred();
95+
$promise = new Promise($this->loop);
9796

98-
$reactRequest->on('error', function (\Exception $error) use ($deferred, $request) {
99-
$deferred->reject(new RequestException(
97+
$reactRequest->on('error', function (\Exception $error) use ($promise, $request) {
98+
$promise->reject(new RequestException(
10099
$error->getMessage(),
101100
$request,
102101
$error
103102
));
104103
});
105104

106-
$reactRequest->on('response', function (ReactResponse $reactResponse = null) use ($deferred, $reactRequest, $request) {
105+
$reactRequest->on('response', function (ReactResponse $reactResponse = null) use ($promise, $request) {
107106
$bodyStream = $this->streamFactory->createStream();
108107
$reactResponse->on('data', function ($data) use (&$bodyStream) {
109108
$bodyStream->write((string) $data);
110109
});
111110

112-
$reactResponse->on('end', function (\Exception $error = null) use ($deferred, $request, $reactResponse, &$bodyStream) {
111+
$reactResponse->on('end', function (\Exception $error = null) use ($promise, $request, $reactResponse, &$bodyStream) {
113112
$response = $this->buildResponse(
114113
$reactResponse,
115114
$bodyStream
116115
);
117116
if (null !== $error) {
118-
$deferred->reject(new HttpException(
117+
$promise->reject(new HttpException(
119118
$error->getMessage(),
120119
$request,
121120
$response,
122121
$error
123122
));
124123
} else {
125-
$deferred->resolve($response);
124+
$promise->resolve($response);
126125
}
127126
});
128127
});
129128

130129
$reactRequest->end((string) $request->getBody());
131130

132-
$promise = new Promise($deferred->promise());
133-
$promise->setLoop($this->loop);
134-
135131
return $promise;
136132
}
137133

src/Promise.php

Lines changed: 96 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Http\Adapter\React;
44

55
use React\EventLoop\LoopInterface;
6-
use React\Promise\PromiseInterface as ReactPromise;
76
use Http\Client\Exception;
87
use Http\Promise\Promise as HttpPromise;
98
use Psr\Http\Message\ResponseInterface;
@@ -12,8 +11,10 @@
1211
* React promise adapter implementation.
1312
*
1413
* @author Stéphane Hulard <[email protected]>
14+
*
15+
* @internal
1516
*/
16-
class Promise implements HttpPromise
17+
final class Promise implements HttpPromise
1718
{
1819
/**
1920
* Promise status.
@@ -22,13 +23,6 @@ class Promise implements HttpPromise
2223
*/
2324
private $state = HttpPromise::PENDING;
2425

25-
/**
26-
* Adapted React promise.
27-
*
28-
* @var ReactPromise
29-
*/
30-
private $promise;
31-
3226
/**
3327
* PSR7 received response.
3428
*
@@ -43,31 +37,26 @@ class Promise implements HttpPromise
4337
*/
4438
private $exception;
4539

40+
/**
41+
* @var callable|null
42+
*/
43+
private $onFulfilled;
44+
45+
/**
46+
* @var callable|null
47+
*/
48+
private $onRejected;
49+
4650
/**
4751
* React Event Loop used for synchronous processing.
4852
*
4953
* @var LoopInterface
5054
*/
5155
private $loop;
5256

53-
/**
54-
* Initialize the promise.
55-
*
56-
* @param ReactPromise $promise
57-
*/
58-
public function __construct(ReactPromise $promise)
57+
public function __construct(LoopInterface $loop)
5958
{
60-
$promise->then(
61-
function (ResponseInterface $response) {
62-
$this->state = HttpPromise::FULFILLED;
63-
$this->response = $response;
64-
},
65-
function (Exception $error) {
66-
$this->state = HttpPromise::REJECTED;
67-
$this->exception = $error;
68-
}
69-
);
70-
$this->promise = $promise;
59+
$this->loop = $loop;
7160
}
7261

7362
/**
@@ -80,49 +69,110 @@ function (Exception $error) {
8069
*/
8170
public function then(callable $onFulfilled = null, callable $onRejected = null)
8271
{
83-
$this->promise->then(function () use ($onFulfilled) {
84-
if (null !== $onFulfilled) {
85-
call_user_func($onFulfilled, $this->response);
72+
$newPromise = new self($this->loop);
73+
74+
$onFulfilled = $onFulfilled !== null ? $onFulfilled : function (ResponseInterface $response) {
75+
return $response;
76+
};
77+
78+
$onRejected = $onRejected !== null ? $onRejected : function (Exception $exception) {
79+
throw $exception;
80+
};
81+
82+
$this->onFulfilled = function (ResponseInterface $response) use ($onFulfilled, $newPromise) {
83+
try {
84+
$newPromise->resolve($onFulfilled($response));
85+
} catch (Exception $exception) {
86+
$newPromise->reject($exception);
8687
}
87-
}, function () use ($onRejected) {
88-
if (null !== $onRejected) {
89-
call_user_func($onRejected, $this->exception);
88+
};
89+
90+
$this->onRejected = function (Exception $exception) use ($onRejected, $newPromise) {
91+
try {
92+
$newPromise->resolve($onRejected($exception));
93+
} catch (Exception $exception) {
94+
$newPromise->reject($exception);
9095
}
91-
});
96+
};
9297

93-
return $this;
98+
if ($this->state === HttpPromise::FULFILLED) {
99+
$this->doResolve($this->response);
100+
}
101+
102+
if ($this->state === HttpPromise::REJECTED) {
103+
$this->doReject($this->exception);
104+
}
105+
106+
return $newPromise;
94107
}
95108

96109
/**
97-
* {@inheritdoc}
110+
* Resolve this promise.
111+
*
112+
* @param ResponseInterface $response
113+
*
114+
* @internal
98115
*/
99-
public function getState()
116+
public function resolve(ResponseInterface $response)
100117
{
101-
return $this->state;
118+
if ($this->state !== HttpPromise::PENDING) {
119+
throw new \RuntimeException('Promise is already resolved');
120+
}
121+
122+
$this->state = HttpPromise::FULFILLED;
123+
$this->response = $response;
124+
$this->doResolve($response);
125+
}
126+
127+
private function doResolve(ResponseInterface $response)
128+
{
129+
$onFulfilled = $this->onFulfilled;
130+
131+
if (null !== $onFulfilled) {
132+
$onFulfilled($response);
133+
}
102134
}
103135

104136
/**
105-
* Set EventLoop used for synchronous processing.
137+
* Reject this promise.
106138
*
107-
* @param LoopInterface $loop
139+
* @param Exception $exception
108140
*
109-
* @return Promise
141+
* @internal
110142
*/
111-
public function setLoop(LoopInterface $loop)
143+
public function reject(Exception $exception)
112144
{
113-
$this->loop = $loop;
145+
if ($this->state !== HttpPromise::PENDING) {
146+
throw new \RuntimeException('Promise is already resolved');
147+
}
148+
149+
$this->state = HttpPromise::REJECTED;
150+
$this->exception = $exception;
151+
$this->doReject($exception);
152+
}
153+
154+
private function doReject(Exception $exception)
155+
{
156+
$onRejected = $this->onRejected;
114157

115-
return $this;
158+
if (null !== $onRejected) {
159+
$onRejected($exception);
160+
}
161+
}
162+
163+
/**
164+
* {@inheritdoc}
165+
*/
166+
public function getState()
167+
{
168+
return $this->state;
116169
}
117170

118171
/**
119172
* {@inheritdoc}
120173
*/
121174
public function wait($unwrap = true)
122175
{
123-
if (null === $this->loop) {
124-
throw new \LogicException('You must set the loop before wait!');
125-
}
126176
while (HttpPromise::PENDING === $this->getState()) {
127177
$this->loop->tick();
128178
}

tests/PromiseTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Http\Adapter\React\Tests;
4+
5+
use GuzzleHttp\Psr7\Response;
6+
use Http\Adapter\React\Promise;
7+
use Http\Adapter\React\ReactFactory;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class PromiseTest extends TestCase
11+
{
12+
private $loop;
13+
14+
public function setUp()
15+
{
16+
$this->loop = ReactFactory::buildEventLoop();
17+
}
18+
19+
public function testChain()
20+
{
21+
$promise = new Promise($this->loop);
22+
$response = new Response(200);
23+
24+
$lastPromise = $promise->then(function (Response $response) {
25+
return $response->withStatus(300);
26+
});
27+
28+
$promise->resolve($response);
29+
$updatedResponse = $lastPromise->wait();
30+
31+
self::assertEquals(200, $response->getStatusCode());
32+
self::assertEquals(300, $updatedResponse->getStatusCode());
33+
}
34+
}

0 commit comments

Comments
 (0)