Skip to content

Commit 62f6b0c

Browse files
authored
Merge pull request #39 from SimonFrings/loop_stop
[2.x] Only stop loop if a pending promise resolves/rejects
2 parents ab03f4d + fcfa37a commit 62f6b0c

File tree

2 files changed

+155
-4
lines changed

2 files changed

+155
-4
lines changed

src/functions.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,25 @@ function await(PromiseInterface $promise)
5454
$resolved = null;
5555
$exception = null;
5656
$rejected = false;
57+
$loopStarted = false;
5758

5859
$promise->then(
59-
function ($c) use (&$resolved, &$wait) {
60+
function ($c) use (&$resolved, &$wait, &$loopStarted) {
6061
$resolved = $c;
6162
$wait = false;
62-
Loop::stop();
63+
64+
if ($loopStarted) {
65+
Loop::stop();
66+
}
6367
},
64-
function ($error) use (&$exception, &$rejected, &$wait) {
68+
function ($error) use (&$exception, &$rejected, &$wait, &$loopStarted) {
6569
$exception = $error;
6670
$rejected = true;
6771
$wait = false;
68-
Loop::stop();
72+
73+
if ($loopStarted) {
74+
Loop::stop();
75+
}
6976
}
7077
);
7178

@@ -74,6 +81,7 @@ function ($error) use (&$exception, &$rejected, &$wait) {
7481
$promise = null;
7582

7683
while ($wait) {
84+
$loopStarted = true;
7785
Loop::run();
7886
}
7987

tests/AwaitTest.php

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,149 @@ public function testAwaitReturnsValueWhenPromiseIsFulfilledEvenWhenOtherTimerSto
8282
$this->assertEquals(2, React\Async\await($promise));
8383
}
8484

85+
public function testAwaitWithAlreadyFulfilledPromiseWillReturnWithoutRunningLoop()
86+
{
87+
$now = true;
88+
89+
Loop::futureTick(function () use (&$now) {
90+
$now = false;
91+
});
92+
93+
$promise = new Promise(function ($resolve) {
94+
$resolve(42);
95+
});
96+
97+
React\Async\await($promise);
98+
$this->assertTrue($now);
99+
}
100+
101+
public function testAwaitWithAlreadyFulfilledPromiseWillReturnWithoutStoppingLoop()
102+
{
103+
$ticks = 0;
104+
105+
$promise = new Promise(function ($resolve) {
106+
$resolve(42);
107+
});
108+
109+
// Loop will execute this tick first
110+
Loop::futureTick(function () use (&$ticks) {
111+
++$ticks;
112+
// Loop will execute this tick third
113+
Loop::futureTick(function () use (&$ticks) {
114+
++$ticks;
115+
});
116+
});
117+
118+
// Loop will execute this tick second
119+
Loop::futureTick(function () use (&$promise){
120+
// await won't stop the loop if promise already resolved -> third tick will trigger
121+
React\Async\await($promise);
122+
});
123+
124+
Loop::run();
125+
126+
$this->assertEquals(2, $ticks);
127+
}
128+
129+
public function testAwaitWithPendingPromiseThatWillResolveWillStopLoopBeforeLastTimerFinishes()
130+
{
131+
$promise = new Promise(function ($resolve) {
132+
Loop::addTimer(0.02, function () use ($resolve) {
133+
$resolve(2);
134+
});
135+
});
136+
137+
$ticks = 0;
138+
139+
// Loop will execute this tick first
140+
Loop::futureTick(function () use (&$ticks) {
141+
++$ticks;
142+
// This timer will never finish because Loop gets stopped by await
143+
// Loop needs to be manually started again to finish this timer
144+
Loop::addTimer(0.04, function () use (&$ticks) {
145+
++$ticks;
146+
});
147+
});
148+
149+
// await stops the loop when promise resolves after 0.02s
150+
Loop::futureTick(function () use (&$promise){
151+
React\Async\await($promise);
152+
});
153+
154+
Loop::run();
155+
156+
// This bahvior exists in v2 & v3 of async, we recommend to use fibers in v4 (PHP>=8.1)
157+
$this->assertEquals(1, $ticks);
158+
}
159+
160+
public function testAwaitWithAlreadyRejectedPromiseWillReturnWithoutStoppingLoop()
161+
{
162+
$ticks = 0;
163+
164+
$promise = new Promise(function ($_, $reject) {
165+
throw new \Exception();
166+
});
167+
168+
// Loop will execute this tick first
169+
Loop::futureTick(function () use (&$ticks) {
170+
++$ticks;
171+
// Loop will execute this tick third
172+
Loop::futureTick(function () use (&$ticks) {
173+
++$ticks;
174+
});
175+
});
176+
177+
// Loop will execute this tick second
178+
Loop::futureTick(function () use (&$promise){
179+
try {
180+
// await won't stop the loop if promise already rejected -> third tick will trigger
181+
React\Async\await($promise);
182+
} catch (\Exception $e) {
183+
// no-op
184+
}
185+
});
186+
187+
Loop::run();
188+
189+
$this->assertEquals(2, $ticks);
190+
}
191+
192+
public function testAwaitWithPendingPromiseThatWillRejectWillStopLoopBeforeLastTimerFinishes()
193+
{
194+
$promise = new Promise(function ($_, $reject) {
195+
Loop::addTimer(0.02, function () use (&$reject) {
196+
$reject(new \Exception());
197+
});
198+
});
199+
200+
$ticks = 0;
201+
202+
// Loop will execute this tick first
203+
Loop::futureTick(function () use (&$ticks) {
204+
++$ticks;
205+
// This timer will never finish because Loop gets stopped by await
206+
// Loop needs to be manually started again to finish this timer
207+
Loop::addTimer(0.04, function () use (&$ticks) {
208+
++$ticks;
209+
});
210+
});
211+
212+
// Loop will execute this tick second
213+
// await stops the loop when promise rejects after 0.02s
214+
Loop::futureTick(function () use (&$promise){
215+
try {
216+
React\Async\await($promise);
217+
} catch (\Exception $e) {
218+
// no-op
219+
}
220+
});
221+
222+
Loop::run();
223+
224+
// This bahvior exists in v2 & v3 of async, we recommend to use fibers in v4 (PHP>=8.1)
225+
$this->assertEquals(1, $ticks);
226+
}
227+
85228
public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise()
86229
{
87230
if (class_exists('React\Promise\When') && PHP_VERSION_ID >= 50400) {

0 commit comments

Comments
 (0)