Skip to content

Commit 603e70b

Browse files
committed
Improve cancellation for coroutines continuing to yield pending promises
1 parent d460d70 commit 603e70b

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

src/functions.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,12 @@ function coroutine(callable $function, ...$args): PromiseInterface
226226

227227
$promise = null;
228228
$deferred = new Deferred(function () use (&$promise) {
229-
if ($promise instanceof CancellablePromiseInterface) {
230-
$promise->cancel();
229+
// cancel pending promise(s) as long as generator function keeps yielding
230+
while ($promise instanceof CancellablePromiseInterface) {
231+
$temp = $promise;
232+
$promise = null;
233+
$temp->cancel();
231234
}
232-
$promise = null;
233235
});
234236

235237
/** @var callable $next */

tests/CoroutineTest.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,41 @@ public function testCoroutineReturnsRejectedPromiseIfFunctionYieldsInvalidValue(
106106
$promise->then(null, $this->expectCallableOnceWith(new \UnexpectedValueException('Expected coroutine to yield React\Promise\PromiseInterface, but got integer')));
107107
}
108108

109+
109110
public function testCoroutineWillCancelPendingPromiseWhenCallingCancelOnResultingPromise()
111+
{
112+
$cancelled = 0;
113+
$promise = coroutine(function () use (&$cancelled) {
114+
yield new Promise(function () use (&$cancelled) {
115+
++$cancelled;
116+
});
117+
});
118+
119+
$promise->cancel();
120+
121+
$this->assertEquals(1, $cancelled);
122+
}
123+
124+
public function testCoroutineWillCancelAllPendingPromisesWhenFunctionContinuesToYieldWhenCallingCancelOnResultingPromise()
110125
{
111126
$promise = coroutine(function () {
127+
$promise = new Promise(function () { }, function () {
128+
throw new \RuntimeException('Frist operation cancelled', 21);
129+
});
130+
131+
try {
132+
yield $promise;
133+
} catch (\RuntimeException $e) {
134+
// ignore exception and continue
135+
}
136+
112137
yield new Promise(function () { }, function () {
113-
throw new \RuntimeException('Operation cancelled', 42);
138+
throw new \RuntimeException('Second operation cancelled', 42);
114139
});
115140
});
116141

117142
$promise->cancel();
118143

119-
$promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Operation cancelled', 42)));
144+
$promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Second operation cancelled', 42)));
120145
}
121146
}

0 commit comments

Comments
 (0)