Skip to content

Commit 08a009e

Browse files
committed
Fast forward resolved/rejected promises with await
This makes `await`ing an already resolved promise significantly faster. Ported from: #18
1 parent ab03f4d commit 08a009e

File tree

2 files changed

+76
-15
lines changed

2 files changed

+76
-15
lines changed

src/functions.php

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,47 @@
5151
function await(PromiseInterface $promise)
5252
{
5353
$wait = true;
54-
$resolved = null;
55-
$exception = null;
54+
$resolved = false;
5655
$rejected = false;
56+
$resolvedValue = null;
57+
$rejectedThrowable = null;
5758

5859
$promise->then(
59-
function ($c) use (&$resolved, &$wait) {
60-
$resolved = $c;
60+
function ($c) use (&$resolved, &$resolvedValue, &$wait) {
61+
$resolvedValue = $c;
62+
$resolved = true;
6163
$wait = false;
6264
Loop::stop();
6365
},
64-
function ($error) use (&$exception, &$rejected, &$wait) {
65-
$exception = $error;
66+
function ($error) use (&$rejected, &$rejectedThrowable, &$wait) {
67+
// promise is rejected with an unexpected value (Promise API v1 or v2 only)
68+
if (!$error instanceof \Exception && !$error instanceof \Throwable) {
69+
$error = new \UnexpectedValueException(
70+
'Promise rejected with unexpected value of type ' . (is_object($error) ? get_class($error) : gettype($error))
71+
);
72+
73+
// avoid garbage references by replacing all closures in call stack.
74+
// what a lovely piece of code!
75+
$r = new \ReflectionProperty('Exception', 'trace');
76+
$r->setAccessible(true);
77+
$trace = $r->getValue($error);
78+
79+
// Exception trace arguments only available when zend.exception_ignore_args is not set
80+
// @codeCoverageIgnoreStart
81+
foreach ($trace as $ti => $one) {
82+
if (isset($one['args'])) {
83+
foreach ($one['args'] as $ai => $arg) {
84+
if ($arg instanceof \Closure) {
85+
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
86+
}
87+
}
88+
}
89+
}
90+
// @codeCoverageIgnoreEnd
91+
$r->setValue($error, $trace);
92+
}
93+
94+
$rejectedThrowable = $error;
6695
$rejected = true;
6796
$wait = false;
6897
Loop::stop();
@@ -73,22 +102,23 @@ function ($error) use (&$exception, &$rejected, &$wait) {
73102
// argument does not show up in the stack trace in PHP 7+ only.
74103
$promise = null;
75104

105+
if ($rejected) {
106+
throw $rejectedThrowable;
107+
}
108+
109+
if ($resolved) {
110+
return $resolvedValue;
111+
}
112+
76113
while ($wait) {
77114
Loop::run();
78115
}
79116

80117
if ($rejected) {
81-
// promise is rejected with an unexpected value (Promise API v1 or v2 only)
82-
if (!$exception instanceof \Exception && !$exception instanceof \Throwable) {
83-
$exception = new \UnexpectedValueException(
84-
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
85-
);
86-
}
87-
88-
throw $exception;
118+
throw $rejectedThrowable;
89119
}
90120

91-
return $resolved;
121+
return $resolvedValue;
92122
}
93123

94124
/**

tests/AwaitTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,37 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWi
145145
$this->assertEquals(0, gc_collect_cycles());
146146
}
147147

148+
public function testAlreadyFulfilledPromiseShouldShortCircuitAndNotRunLoop()
149+
{
150+
for ($i = 0; $i < 6; $i++) {
151+
$this->assertSame($i, React\Async\await(React\Promise\resolve($i)));
152+
}
153+
}
154+
155+
public function testPendingPromiseShouldNotShortCircuitAndRunLoop()
156+
{
157+
Loop::futureTick($this->expectCallableOnce());
158+
159+
$this->assertSame(1, React\Async\await(new Promise(function (callable $resolve) {
160+
Loop::futureTick(function () use ($resolve) {
161+
$resolve(1);
162+
});
163+
})));
164+
}
165+
166+
public function testPendingPromiseShouldNotShortCircuitAndRunLoopAndThrowOnRejection()
167+
{
168+
Loop::futureTick($this->expectCallableOnce());
169+
170+
$this->setExpectedException('Exception', 'test');
171+
172+
$this->assertSame(1, React\Async\await(new Promise(function (callable $resolve, callable $reject) {
173+
Loop::futureTick(function () use ($reject) {
174+
$reject(new \Exception('test'));
175+
});
176+
})));
177+
}
178+
148179
public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
149180
{
150181
if (method_exists($this, 'expectException')) {

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