Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,16 @@ If there are no other (async) tasks, this will behave similar to `sleep()`.

#### await()

The `await(PromiseInterface $promise, LoopInterface $loop)` method can be used to block waiting for the given $promise to resolve.
The `await(PromiseInterface $promise, LoopInterface $loop, $timeout = null)`
function can be used to block waiting for the given $promise to resolve.

```php
$result = Block\await($promise, $loop);
```

Once the promise is resolved, this will return whatever the promise resolves to.

If the promises is being rejected, this will fail and throw an `Exception`.
Once the promise is rejected, this will throw whatever the promise rejected with.

```php
try {
Expand All @@ -128,9 +129,16 @@ try {
}
```

If no $timeout is given and the promise stays pending, then this will
potentially wait/block forever until the promise is settled.

If a $timeout is given and the promise is still pending once the timeout
triggers, this will `cancel()` the promise and throw a `TimeoutException`.

#### awaitAny()

The `awaitAny(array $promises, LoopInterface $loop)` method can be used to wait for ANY of the given promises to resolve.
The `awaitAny(array $promises, LoopInterface $loop, $timeout = null)`
function can be used to wait for ANY of the given promises to resolve.

```php
$promises = array(
Expand All @@ -148,9 +156,16 @@ remaining promises and return whatever the first promise resolves to.

If ALL promises fail to resolve, this will fail and throw an `Exception`.

If no $timeout is given and either promise stays pending, then this will
potentially wait/block forever until the last promise is settled.

If a $timeout is given and either promise is still pending once the timeout
triggers, this will `cancel()` all pending promises and throw a `TimeoutException`.

#### awaitAll()

The `awaitAll(array $promises, LoopInterface $loop)` method can be used to wait for ALL of the given promises to resolve.
The `awaitAll(array $promises, LoopInterface $loop, $timeout = null)`
function can be used to wait for ALL of the given promises to resolve.

```php
$promises = array(
Expand All @@ -170,6 +185,12 @@ be used to correlate the return array to the promises passed.
If ANY promise fails to resolve, this will try to `cancel()` all
remaining promises and throw an `Exception`.

If no $timeout is given and either promise stays pending, then this will
potentially wait/block forever until the last promise is settled.

If a $timeout is given and either promise is still pending once the timeout
triggers, this will `cancel()` all pending promises and throw a `TimeoutException`.

## Install

The recommended way to install this library is [through composer](http://getcomposer.org).
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"require": {
"php": ">=5.3",
"react/event-loop": "0.4.*|0.3.*",
"react/promise": "~2.1|~1.2"
"react/promise": "~2.1|~1.2",
"react/promise-timer": "~1.0"
}
}
65 changes: 49 additions & 16 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use UnderflowException;
use Exception;
use React\Promise;
use React\Promise\Timer;
use React\Promise\Timer\TimeoutException;

/**
* wait/sleep for $time seconds
Expand All @@ -17,31 +19,39 @@
*/
function sleep($time, LoopInterface $loop)
{
$wait = true;
$loop->addTimer($time, function () use ($loop, &$wait) {
$loop->stop();
$wait = false;
});

do {
$loop->run();
} while($wait);
await(Timer\resolve($time, $loop), $loop);
}

/**
* block waiting for the given $promise to resolve
*
* Once the promise is resolved, this will return whatever the promise resolves to.
*
* Once the promise is rejected, this will throw whatever the promise rejected with.
*
* If no $timeout is given and the promise stays pending, then this will
* potentially wait/block forever until the promise is settled.
*
* If a $timeout is given and the promise is still pending once the timeout
* triggers, this will cancel() the promise and throw a `TimeoutException`.
*
* @param PromiseInterface $promise
* @param LoopInterface $loop
* @param null|float $timeout (optional) maximum timeout in seconds or null=wait forever
* @return mixed returns whatever the promise resolves to
* @throws Exception when the promise is rejected
* @throws TimeoutException if the $timeout is given and triggers
*/
function await(PromiseInterface $promise, LoopInterface $loop)
function await(PromiseInterface $promise, LoopInterface $loop, $timeout = null)
{
$wait = true;
$resolved = null;
$exception = null;

if ($timeout !== null) {
$promise = Timer\timeout($promise, $timeout, $loop);
}

$promise->then(
function ($c) use (&$resolved, &$wait, $loop) {
$resolved = $c;
Expand Down Expand Up @@ -74,12 +84,20 @@ function ($error) use (&$exception, &$wait, $loop) {
*
* If ALL promises fail to resolve, this will fail and throw an Exception.
*
* If no $timeout is given and either promise stays pending, then this will
* potentially wait/block forever until the last promise is settled.
*
* If a $timeout is given and either promise is still pending once the timeout
* triggers, this will cancel() all pending promises and throw a `TimeoutException`.
*
* @param array $promises
* @param LoopInterface $loop
* @param null|float $timeout (optional) maximum timeout in seconds or null=wait forever
* @return mixed returns whatever the first promise resolves to
* @throws Exception if ALL promises are rejected
* @throws TimeoutException if the $timeout is given and triggers
*/
function awaitAny(array $promises, LoopInterface $loop)
function awaitAny(array $promises, LoopInterface $loop, $timeout = null)
{
try {
// Promise\any() does not cope with an empty input array, so reject this here
Expand All @@ -90,10 +108,17 @@ function awaitAny(array $promises, LoopInterface $loop)
$ret = await(Promise\any($promises)->then(null, function () {
// rejects with an array of rejection reasons => reject with Exception instead
throw new Exception('All promises rejected');
}), $loop);
}), $loop, $timeout);
} catch (TimeoutException $e) {
// the timeout fired
// => try to cancel all promises (rejected ones will be ignored anyway)
_cancelAllPromises($promises);

throw $e;
} catch (Exception $e) {
// if the above throws, then ALL promises are already rejected
// (attention: this does not apply once timeout comes into play)
// => try to cancel all promises (rejected ones will be ignored anyway)
_cancelAllPromises($promises);

throw new UnderflowException('No promise could resolve', 0, $e);
}
Expand All @@ -115,17 +140,25 @@ function awaitAny(array $promises, LoopInterface $loop)
* If ANY promise fails to resolve, this will try to cancel() all
* remaining promises and throw an Exception.
*
* If no $timeout is given and either promise stays pending, then this will
* potentially wait/block forever until the last promise is settled.
*
* If a $timeout is given and either promise is still pending once the timeout
* triggers, this will cancel() all pending promises and throw a `TimeoutException`.
*
* @param array $promises
* @param LoopInterface $loop
* @param null|float $timeout (optional) maximum timeout in seconds or null=wait forever
* @return array returns an array with whatever each promise resolves to
* @throws Exception when ANY promise is rejected
* @throws TimeoutException if the $timeout is given and triggers
*/
function awaitAll(array $promises, LoopInterface $loop)
function awaitAll(array $promises, LoopInterface $loop, $timeout = null)
{
try {
return await(Promise\all($promises), $loop);
return await(Promise\all($promises), $loop, $timeout);
} catch (Exception $e) {
// ANY of the given promises rejected
// ANY of the given promises rejected or the timeout fired
// => try to cancel all promises (rejected ones will be ignored anyway)
_cancelAllPromises($promises);

Expand Down
15 changes: 15 additions & 0 deletions tests/FunctionAwaitAllTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Clue\React\Block;
use React\Promise;
use React\Promise\Timer\TimeoutException;

class FunctionAwaitAllTest extends TestCase
{
Expand Down Expand Up @@ -70,4 +71,18 @@ public function testAwaitAllWithRejectedWillCancelPending()
$this->assertTrue($cancelled);
}
}

public function testAwaitAllPendingWillThrowAndCallCancellerOnTimeout()
{
$cancelled = false;
$promise = new Promise\Promise(function () { }, function () use (&$cancelled) {
$cancelled = true;
});

try {
Block\awaitAll(array($promise), $this->loop, 0.001);
} catch (TimeoutException $expected) {
$this->assertTrue($cancelled);
}
}
}
15 changes: 15 additions & 0 deletions tests/FunctionAwaitAnyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Clue\React\Block;
use React\Promise\Deferred;
use React\Promise;
use React\Promise\Timer\TimeoutException;

class FunctionAwaitAnyTest extends TestCase
{
Expand Down Expand Up @@ -80,4 +81,18 @@ public function testAwaitAnyWithResolvedWillCancelPending()
$this->assertEquals(2, Block\awaitAny($all, $this->loop));
$this->assertTrue($cancelled);
}

public function testAwaitAnyPendingWillThrowAndCallCancellerOnTimeout()
{
$cancelled = false;
$promise = new Promise\Promise(function () { }, function () use (&$cancelled) {
$cancelled = true;
});

try {
Block\awaitAny(array($promise), $this->loop, 0.001);
} catch (TimeoutException $expected) {
$this->assertTrue($cancelled);
}
}
}
36 changes: 36 additions & 0 deletions tests/FunctionAwaitTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

use Clue\React\Block;
use React\Promise;
use React\Promise\Timer\TimeoutException;

class FunctionAwaitTest extends TestCase
{
Expand All @@ -26,4 +28,38 @@ public function testAwaitOneInterrupted()

$this->assertEquals(2, Block\await($promise, $this->loop));
}

public function testAwaitOncePendingWillThrowOnTimeout()
{
$promise = new Promise\Promise(function () { });

$this->setExpectedException('React\Promise\Timer\TimeoutException');
Block\await($promise, $this->loop, 0.001);
}

public function testAwaitOncePendingWillThrowAndCallCancellerOnTimeout()
{
$cancelled = false;
$promise = new Promise\Promise(function () { }, function () use (&$cancelled) {
$cancelled = true;
});

try {
Block\await($promise, $this->loop, 0.001);
} catch (TimeoutException $expected) {
$this->assertTrue($cancelled);
}
}

public function testAwaitOnceWithTimeoutWillResolvemmediatelyAndCleanUpTimeout()
{
$promise = Promise\resolve(true);

$time = microtime(true);
Block\await($promise, $this->loop, 5.0);
$this->loop->run();
$time = microtime(true) - $time;

$this->assertLessThan(0.1, $time);
}
}