Skip to content

Commit 949ee8a

Browse files
joelwurtzdbu
authored andcommitted
Use deferred to be full async in retry (#101)
Use deferred to be full async in retry
1 parent f6901d0 commit 949ee8a

File tree

4 files changed

+166
-10
lines changed

4 files changed

+166
-10
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Changed
1010

1111
- AddPathPlugin no longer add prefix multiple times if a request is restarted - it now only adds the prefix if that request chain has not yet passed through the AddPathPlugin
12+
- RetryPlugin no longer wait for retried requests and use a deferred promise instead
1213

1314
### Fixed
1415

spec/Plugin/RetryPluginSpec.php

+12-5
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ function it_returns_response(RequestInterface $request, ResponseInterface $respo
3030
}
3131
};
3232

33-
$this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise');
33+
$promise = $this->handleRequest($request, $next, function () {});
34+
$promise->shouldReturnAnInstanceOf('Http\Client\Common\Deferred');
35+
$promise->wait()->shouldReturn($response);
3436
}
3537

3638
function it_throws_exception_on_multiple_exceptions(RequestInterface $request)
@@ -53,7 +55,7 @@ function it_throws_exception_on_multiple_exceptions(RequestInterface $request)
5355
};
5456

5557
$promise = $this->handleRequest($request, $next, function () {});
56-
$promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpRejectedPromise');
58+
$promise->shouldReturnAnInstanceOf('Http\Client\Common\Deferred');
5759
$promise->shouldThrow($exception2)->duringWait();
5860
}
5961

@@ -76,7 +78,7 @@ function it_returns_response_on_second_try(RequestInterface $request, ResponseIn
7678
};
7779

7880
$promise = $this->handleRequest($request, $next, function () {});
79-
$promise->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise');
81+
$promise->shouldReturnAnInstanceOf('Http\Client\Common\Deferred');
8082
$promise->wait()->shouldReturn($response);
8183
}
8284

@@ -98,8 +100,13 @@ function it_does_not_keep_history_of_old_failure(RequestInterface $request, Resp
98100
}
99101
};
100102

101-
$this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise');
102-
$this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise');
103+
$promise = $this->handleRequest($request, $next, function () {});
104+
$promise->shouldReturnAnInstanceOf('Http\Client\Common\Deferred');
105+
$promise->wait()->shouldReturn($response);
106+
107+
$promise = $this->handleRequest($request, $next, function () {});
108+
$promise->shouldReturnAnInstanceOf('Http\Client\Common\Deferred');
109+
$promise->wait()->shouldReturn($response);
103110
}
104111

105112
function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception)

src/Deferred.php

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
namespace Http\Client\Common;
4+
5+
use Http\Client\Exception;
6+
use Http\Promise\Promise;
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
/**
10+
* A deferred allow to return a promise which has not been resolved yet.
11+
*/
12+
class Deferred implements Promise
13+
{
14+
private $value;
15+
16+
private $failure;
17+
18+
private $state;
19+
20+
private $waitCallback;
21+
22+
private $onFulfilledCallbacks;
23+
24+
private $onRejectedCallbacks;
25+
26+
public function __construct(callable $waitCallback)
27+
{
28+
$this->waitCallback = $waitCallback;
29+
$this->state = Promise::PENDING;
30+
$this->onFulfilledCallbacks = [];
31+
$this->onRejectedCallbacks = [];
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function then(callable $onFulfilled = null, callable $onRejected = null)
38+
{
39+
$deferred = new self($this->waitCallback);
40+
41+
$this->onFulfilledCallbacks[] = function (ResponseInterface $response) use ($onFulfilled, $deferred) {
42+
try {
43+
if (null !== $onFulfilled) {
44+
$response = $onFulfilled($response);
45+
}
46+
$deferred->resolve($response);
47+
} catch (Exception $exception) {
48+
$deferred->reject($exception);
49+
}
50+
};
51+
52+
$this->onRejectedCallbacks[] = function (Exception $exception) use ($onRejected, $deferred) {
53+
try {
54+
if (null !== $onRejected) {
55+
$response = $onRejected($exception);
56+
$deferred->resolve($response);
57+
58+
return;
59+
}
60+
$deferred->reject($exception);
61+
} catch (Exception $newException) {
62+
$deferred->reject($newException);
63+
}
64+
};
65+
66+
return $deferred;
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function getState()
73+
{
74+
return $this->state;
75+
}
76+
77+
/**
78+
* Resolve this deferred with a Response.
79+
*/
80+
public function resolve(ResponseInterface $response)
81+
{
82+
if (self::PENDING !== $this->state) {
83+
return;
84+
}
85+
86+
$this->value = $response;
87+
$this->state = self::FULFILLED;
88+
89+
foreach ($this->onFulfilledCallbacks as $onFulfilledCallback) {
90+
$onFulfilledCallback($response);
91+
}
92+
}
93+
94+
/**
95+
* Reject this deferred with an Exception.
96+
*/
97+
public function reject(Exception $exception)
98+
{
99+
if (self::PENDING !== $this->state) {
100+
return;
101+
}
102+
103+
$this->failure = $exception;
104+
$this->state = self::REJECTED;
105+
106+
foreach ($this->onRejectedCallbacks as $onRejectedCallback) {
107+
$onRejectedCallback($exception);
108+
}
109+
}
110+
111+
/**
112+
* {@inheritdoc}
113+
*/
114+
public function wait($unwrap = true)
115+
{
116+
if (self::PENDING === $this->state) {
117+
$callback = $this->waitCallback;
118+
$callback();
119+
}
120+
121+
if (!$unwrap) {
122+
return;
123+
}
124+
125+
if (self::FULFILLED === $this->state) {
126+
return $this->value;
127+
}
128+
129+
throw $this->failure;
130+
}
131+
}

src/Plugin/RetryPlugin.php

+22-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Http\Client\Common\Plugin;
44

5+
use Http\Client\Common\Deferred;
56
use Http\Client\Common\Plugin;
67
use Http\Client\Exception;
78
use Psr\Http\Message\RequestInterface;
@@ -76,20 +77,31 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
7677
{
7778
$chainIdentifier = spl_object_hash((object) $first);
7879

79-
return $next($request)->then(function (ResponseInterface $response) use ($request, $chainIdentifier) {
80+
$promise = $next($request);
81+
$deferred = new Deferred(function () use ($promise) {
82+
$promise->wait(false);
83+
});
84+
85+
$onFulfilled = function (ResponseInterface $response) use ($chainIdentifier, $deferred) {
8086
if (array_key_exists($chainIdentifier, $this->retryStorage)) {
8187
unset($this->retryStorage[$chainIdentifier]);
8288
}
8389

90+
$deferred->resolve($response);
91+
8492
return $response;
85-
}, function (Exception $exception) use ($request, $next, $first, $chainIdentifier) {
93+
};
94+
95+
$onRejected = function (Exception $exception) use ($request, $next, $onFulfilled, &$onRejected, $chainIdentifier, $deferred) {
8696
if (!array_key_exists($chainIdentifier, $this->retryStorage)) {
8797
$this->retryStorage[$chainIdentifier] = 0;
8898
}
8999

90100
if ($this->retryStorage[$chainIdentifier] >= $this->retry) {
91101
unset($this->retryStorage[$chainIdentifier]);
92102

103+
$deferred->reject($exception);
104+
93105
throw $exception;
94106
}
95107

@@ -102,10 +114,15 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
102114

103115
// Retry in synchrone
104116
++$this->retryStorage[$chainIdentifier];
105-
$promise = $this->handleRequest($request, $next, $first);
106117

107-
return $promise->wait();
108-
});
118+
$next($request)->then($onFulfilled, $onRejected);
119+
120+
throw $exception;
121+
};
122+
123+
$promise->then($onFulfilled, $onRejected);
124+
125+
return $deferred;
109126
}
110127

111128
/**

0 commit comments

Comments
 (0)