Skip to content

Commit a112d86

Browse files
authored
Merge pull request #9 from clue-labs/cancellation
2 parents 98ae760 + 36eb448 commit a112d86

File tree

4 files changed

+155
-33
lines changed

4 files changed

+155
-33
lines changed

src/functions.php

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace React\Async;
44

55
use React\EventLoop\Loop;
6+
use React\Promise\CancellablePromiseInterface;
67
use React\Promise\Deferred;
78
use React\Promise\PromiseInterface;
89

@@ -96,46 +97,53 @@ function ($error) use (&$exception, &$rejected, &$wait) {
9697
*/
9798
function parallel(array $tasks)
9899
{
99-
$deferred = new Deferred();
100-
$results = array();
101-
$errors = array();
102-
103-
$done = function () use (&$results, &$errors, $deferred) {
104-
if (count($errors)) {
105-
$deferred->reject(array_shift($errors));
106-
return;
100+
$pending = array();
101+
$deferred = new Deferred(function () use (&$pending) {
102+
foreach ($pending as $promise) {
103+
if ($promise instanceof CancellablePromiseInterface) {
104+
$promise->cancel();
105+
}
107106
}
108-
109-
$deferred->resolve($results);
110-
};
107+
$pending = array();
108+
});
109+
$results = array();
110+
$errored = false;
111111

112112
$numTasks = count($tasks);
113-
114113
if (0 === $numTasks) {
115-
$done();
114+
$deferred->resolve($results);
116115
}
117116

118-
$checkDone = function () use (&$results, &$errors, $numTasks, $done) {
119-
if ($numTasks === count($results) + count($errors)) {
120-
$done();
121-
}
122-
};
117+
$taskErrback = function ($error) use (&$pending, $deferred, &$errored) {
118+
$errored = true;
119+
$deferred->reject($error);
123120

124-
$taskErrback = function ($error) use (&$errors, $checkDone) {
125-
$errors[] = $error;
126-
$checkDone();
121+
foreach ($pending as $promise) {
122+
if ($promise instanceof CancellablePromiseInterface) {
123+
$promise->cancel();
124+
}
125+
}
126+
$pending = array();
127127
};
128128

129129
foreach ($tasks as $i => $task) {
130-
$taskCallback = function ($result) use (&$results, $i, $checkDone) {
130+
$taskCallback = function ($result) use (&$results, &$pending, $numTasks, $i, $deferred) {
131131
$results[$i] = $result;
132-
$checkDone();
132+
133+
if (count($results) === $numTasks) {
134+
$deferred->resolve($results);
135+
}
133136
};
134137

135138
$promise = call_user_func($task);
136139
assert($promise instanceof PromiseInterface);
140+
$pending[$i] = $promise;
137141

138142
$promise->then($taskCallback, $taskErrback);
143+
144+
if ($errored) {
145+
break;
146+
}
139147
}
140148

141149
return $deferred->promise();
@@ -147,7 +155,13 @@ function parallel(array $tasks)
147155
*/
148156
function series(array $tasks)
149157
{
150-
$deferred = new Deferred();
158+
$pending = null;
159+
$deferred = new Deferred(function () use (&$pending) {
160+
if ($pending instanceof CancellablePromiseInterface) {
161+
$pending->cancel();
162+
}
163+
$pending = null;
164+
});
151165
$results = array();
152166

153167
/** @var callable():void $next */
@@ -156,7 +170,7 @@ function series(array $tasks)
156170
$next();
157171
};
158172

159-
$next = function () use (&$tasks, $taskCallback, $deferred, &$results) {
173+
$next = function () use (&$tasks, $taskCallback, $deferred, &$results, &$pending) {
160174
if (0 === count($tasks)) {
161175
$deferred->resolve($results);
162176
return;
@@ -165,6 +179,7 @@ function series(array $tasks)
165179
$task = array_shift($tasks);
166180
$promise = call_user_func($task);
167181
assert($promise instanceof PromiseInterface);
182+
$pending = $promise;
168183

169184
$promise->then($taskCallback, array($deferred, 'reject'));
170185
};
@@ -180,10 +195,16 @@ function series(array $tasks)
180195
*/
181196
function waterfall(array $tasks)
182197
{
183-
$deferred = new Deferred();
198+
$pending = null;
199+
$deferred = new Deferred(function () use (&$pending) {
200+
if ($pending instanceof CancellablePromiseInterface) {
201+
$pending->cancel();
202+
}
203+
$pending = null;
204+
});
184205

185206
/** @var callable $next */
186-
$next = function ($value = null) use (&$tasks, &$next, $deferred) {
207+
$next = function ($value = null) use (&$tasks, &$next, $deferred, &$pending) {
187208
if (0 === count($tasks)) {
188209
$deferred->resolve($value);
189210
return;
@@ -192,6 +213,7 @@ function waterfall(array $tasks)
192213
$task = array_shift($tasks);
193214
$promise = call_user_func_array($task, func_get_args());
194215
assert($promise instanceof PromiseInterface);
216+
$pending = $promise;
195217

196218
$promise->then($next, array($deferred, 'reject'));
197219
};

tests/ParallelTest.php

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function () {
2929
},
3030
function () {
3131
return new Promise(function ($resolve) {
32-
Loop::addTimer(0.1, function () use ($resolve) {
32+
Loop::addTimer(0.11, function () use ($resolve) {
3333
$resolve('bar');
3434
});
3535
});
@@ -49,7 +49,7 @@ function () {
4949
$timer->assertInRange(0.1, 0.2);
5050
}
5151

52-
public function testParallelWithError()
52+
public function testParallelWithErrorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks()
5353
{
5454
$called = 0;
5555

@@ -60,7 +60,8 @@ function () use (&$called) {
6060
$resolve('foo');
6161
});
6262
},
63-
function () {
63+
function () use (&$called) {
64+
$called++;
6465
return new Promise(function () {
6566
throw new \RuntimeException('whoops');
6667
});
@@ -80,7 +81,59 @@ function () use (&$called) {
8081
$this->assertSame(2, $called);
8182
}
8283

83-
public function testParallelWithDelayedError()
84+
public function testParallelWithErrorWillCancelPendingPromises()
85+
{
86+
$cancelled = 0;
87+
88+
$tasks = array(
89+
function () use (&$cancelled) {
90+
return new Promise(function () { }, function () use (&$cancelled) {
91+
$cancelled++;
92+
});
93+
},
94+
function () {
95+
return new Promise(function () {
96+
throw new \RuntimeException('whoops');
97+
});
98+
},
99+
function () use (&$cancelled) {
100+
return new Promise(function () { }, function () use (&$cancelled) {
101+
$cancelled++;
102+
});
103+
}
104+
);
105+
106+
$promise = React\Async\parallel($tasks);
107+
108+
$promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('whoops')));
109+
110+
$this->assertSame(1, $cancelled);
111+
}
112+
113+
public function testParallelWillCancelPendingPromisesWhenCallingCancelOnResultingPromise()
114+
{
115+
$cancelled = 0;
116+
117+
$tasks = array(
118+
function () use (&$cancelled) {
119+
return new Promise(function () { }, function () use (&$cancelled) {
120+
$cancelled++;
121+
});
122+
},
123+
function () use (&$cancelled) {
124+
return new Promise(function () { }, function () use (&$cancelled) {
125+
$cancelled++;
126+
});
127+
}
128+
);
129+
130+
$promise = React\Async\parallel($tasks);
131+
$promise->cancel();
132+
133+
$this->assertSame(2, $cancelled);
134+
}
135+
136+
public function testParallelWithDelayedErrorReturnsPromiseRejectedWithExceptionFromTask()
84137
{
85138
$called = 0;
86139

@@ -91,7 +144,8 @@ function () use (&$called) {
91144
$resolve('foo');
92145
});
93146
},
94-
function () {
147+
function () use (&$called) {
148+
$called++;
95149
return new Promise(function ($_, $reject) {
96150
Loop::addTimer(0.001, function () use ($reject) {
97151
$reject(new \RuntimeException('whoops'));
@@ -112,6 +166,6 @@ function () use (&$called) {
112166

113167
Loop::run();
114168

115-
$this->assertSame(2, $called);
169+
$this->assertSame(3, $called);
116170
}
117171
}

tests/SeriesTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,27 @@ function () use (&$called) {
7979

8080
$this->assertSame(1, $called);
8181
}
82+
83+
public function testSeriesWillCancelFirstPendingPromiseWhenCallingCancelOnResultingPromise()
84+
{
85+
$cancelled = 0;
86+
87+
$tasks = array(
88+
function () {
89+
return new Promise(function ($resolve) {
90+
$resolve();
91+
});
92+
},
93+
function () use (&$cancelled) {
94+
return new Promise(function () { }, function () use (&$cancelled) {
95+
$cancelled++;
96+
});
97+
}
98+
);
99+
100+
$promise = React\Async\series($tasks);
101+
$promise->cancel();
102+
103+
$this->assertSame(1, $cancelled);
104+
}
82105
}

tests/WaterfallTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,27 @@ function () use (&$called) {
8686

8787
$this->assertSame(1, $called);
8888
}
89+
90+
public function testWaterfallWillCancelFirstPendingPromiseWhenCallingCancelOnResultingPromise()
91+
{
92+
$cancelled = 0;
93+
94+
$tasks = array(
95+
function () {
96+
return new Promise(function ($resolve) {
97+
$resolve();
98+
});
99+
},
100+
function () use (&$cancelled) {
101+
return new Promise(function () { }, function () use (&$cancelled) {
102+
$cancelled++;
103+
});
104+
}
105+
);
106+
107+
$promise = React\Async\waterfall($tasks);
108+
$promise->cancel();
109+
110+
$this->assertSame(1, $cancelled);
111+
}
89112
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy