Skip to content

Commit c97ceb3

Browse files
authored
Merge pull request #94 from reactphp/2.x-circular-references-following-promise
Fix circular references when resolving with a promise which follows itself
2 parents b759442 + 28b5600 commit c97ceb3

File tree

2 files changed

+51
-15
lines changed

2 files changed

+51
-15
lines changed

src/Promise.php

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function __construct(callable $resolver, callable $canceller = null)
2222
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
2323
{
2424
if (null !== $this->result) {
25-
return $this->result()->then($onFulfilled, $onRejected, $onProgress);
25+
return $this->result->then($onFulfilled, $onRejected, $onProgress);
2626
}
2727

2828
if (null === $this->canceller) {
@@ -43,7 +43,7 @@ public function then(callable $onFulfilled = null, callable $onRejected = null,
4343
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
4444
{
4545
if (null !== $this->result) {
46-
return $this->result()->done($onFulfilled, $onRejected, $onProgress);
46+
return $this->result->done($onFulfilled, $onRejected, $onProgress);
4747
}
4848

4949
$this->handlers[] = function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
@@ -155,15 +155,7 @@ private function notify($update = null)
155155

156156
private function settle(ExtendedPromiseInterface $promise)
157157
{
158-
if ($promise instanceof LazyPromise) {
159-
$promise = $promise->promise();
160-
}
161-
162-
if ($promise === $this) {
163-
$promise = new RejectedPromise(
164-
new \LogicException('Cannot resolve a promise with itself.')
165-
);
166-
}
158+
$promise = $this->unwrap($promise);
167159

168160
$handlers = $this->handlers;
169161

@@ -175,13 +167,30 @@ private function settle(ExtendedPromiseInterface $promise)
175167
}
176168
}
177169

178-
private function result()
170+
private function unwrap($promise)
179171
{
180-
while ($this->result instanceof self && null !== $this->result->result) {
181-
$this->result = $this->result->result;
172+
$promise = $this->extract($promise);
173+
174+
while ($promise instanceof self && null !== $promise->result) {
175+
$promise = $this->extract($promise->result);
176+
}
177+
178+
return $promise;
179+
}
180+
181+
private function extract($promise)
182+
{
183+
if ($promise instanceof LazyPromise) {
184+
$promise = $promise->promise();
185+
}
186+
187+
if ($promise === $this) {
188+
return new RejectedPromise(
189+
new \LogicException('Cannot resolve a promise with itself.')
190+
);
182191
}
183192

184-
return $this->result;
193+
return $promise;
185194
}
186195

187196
private function call(callable $callback)

tests/PromiseTest/ResolveTestTrait.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,33 @@ public function resolveShouldRejectWhenResolvedWithItself()
134134
$adapter->resolve($adapter->promise());
135135
}
136136

137+
/**
138+
* @test
139+
*/
140+
public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself()
141+
{
142+
$adapter1 = $this->getPromiseTestAdapter();
143+
$adapter2 = $this->getPromiseTestAdapter();
144+
145+
$mock = $this->createCallableMock();
146+
$mock
147+
->expects($this->once())
148+
->method('__invoke')
149+
->with(new \LogicException('Cannot resolve a promise with itself.'));
150+
151+
$promise1 = $adapter1->promise();
152+
153+
$promise2 = $adapter2->promise();
154+
155+
$promise2->then(
156+
$this->expectCallableNever(),
157+
$mock
158+
);
159+
160+
$adapter1->resolve($promise2);
161+
$adapter2->resolve($promise1);
162+
}
163+
137164
/** @test */
138165
public function doneShouldInvokeFulfillmentHandler()
139166
{

0 commit comments

Comments
 (0)