1
0
mirror of https://github.com/danog/amp.git synced 2024-11-30 04:29:08 +01:00

Lots of changes ... see CHANGELOG for details

This commit is contained in:
Daniel Lowrey 2014-12-01 19:45:57 -05:00
parent 0d7ab5fc6c
commit f32d088c23
17 changed files with 736 additions and 792 deletions

View File

@ -1,21 +1,36 @@
### master
- Remove `Combinator` class in favor of combinator functions
- Remove `Resolver` class
- Remove superfluous Reactor function analogs
- Add `Reactor::onError()` exception handling hook
- Correctly exit `UvReactor` and `LibeventReactor` run loop when no outstanding watchers
remain active
- All `Promisor` implementations now require a `Reactor` constructor parameter.
Previously these implementations would lazy-inject the global singleton event
reactor instance if no reactor parameter was specified in the constructor.
- Add optional boolean `Reactor::tick($noWait)` parameter
**Additions**
- Added `Reactor::onError()` exception handling hook
- Added optional boolean `$noWait` parameter to `Reactor::tick($noWait)`
- Added `Amp\getReactor()` and `Amp\chooseReactor()` functions
**Bugfixes:**
- Correctly break out of the `NativeReactor` run loop immediately when
`Reactor::stop()` invoked inside immediately watchers.
`Reactor::stop()` invoked inside immediately watchers
- Correctly exit `UvReactor` and `LibeventReactor` run loop when no outstanding
watchers remain active
**Removals:**
- Removed `Combinator` class in favor of combinator functions
- Removed `Resolver` class, use `GeneratorResolver` trait internally
- `Promisor` implementations no longer have any knowledge of the event reactor.
**Deprecations:**
- Deprecated `Promise::wait()`. New code should use `Amp\wait()` to synchronously
wait for promise completion
- Deprecated `Amp\reactor()` function. New code should use `Amp\getReactor()`
instead
- The `ReactorFactory` class is deprecated and scheduled for removal. Please use
the `Amp\getReactor()` function instead of `ReactorFactory::select()`
> **BC BREAKS:**
- All the changes listed above are BC breaks -- please update code accordingly :)
- None
v0.14.0
-------

View File

@ -39,15 +39,15 @@ class Failure implements Promise {
}
/**
* Wait for Future value resolution
*
* NOTE: because this object represents a failed Promise it will *always* immediately throw the
* exception responsible for resolution failure.
*
* @throws \Exception
* @return void
* This method is deprecated. New code should use Amp\wait($promise) instead.
*/
public function wait() {
trigger_error(
'Amp\\Promise::wait() is deprecated and scheduled for removal. ' .
'Please update code to use Amp\\wait($promise) instead.',
E_USER_DEPRECATED
);
throw $this->error;
}
}

View File

@ -3,21 +3,12 @@
namespace Amp;
class Future implements Promisor, Promise {
private $reactor;
private $isWaiting = false;
private $isResolved = false;
private $watchers = [];
private $whens = [];
private $error;
private $result;
/**
* @param \Amp\Reactor $reactor
*/
public function __construct(Reactor $reactor) {
$this->reactor = $reactor;
}
/**
* Retrieve the Promise placeholder for this deferred value
*
@ -64,32 +55,26 @@ class Future implements Promisor, Promise {
}
/**
* Block script execution indefinitely until the promise resolves
*
* @throws \Exception
* @return mixed
* This method is deprecated. New code should use Amp\wait($promise) instead.
*/
public function wait() {
if ($this->error) {
throw $this->error;
} elseif ($this->isResolved) {
return $this->result;
}
trigger_error(
'Amp\\Promise::wait() is deprecated and scheduled for removal. ' .
'Please update code to use Amp\\wait($promise) instead.',
E_USER_DEPRECATED
);
$resolvedError;
$resolvedResult;
$this->whens[] = function($error, $result) use (&$resolvedError, &$resolvedResult) {
$isWaiting = true;
$resolvedError = $resolvedResult = null;
$this->when(function($error, $result) use (&$isWaiting, &$resolvedError, &$resolvedResult) {
$isWaiting = false;
$resolvedError = $error;
$resolvedResult = $result;
$this->isWaiting = false;
};
$this->isWaiting = true;
while ($this->isWaiting) {
$this->reactor->tick();
});
$reactor = getReactor();
while ($isWaiting) {
$reactor->tick();
}
if ($resolvedError) {
throw $resolvedError;
}

View File

@ -13,11 +13,8 @@ class PrivateFuture implements Promisor {
private $updater;
private $promise;
/**
* @param \Amp\Reactor $reactor
*/
public function __construct(Reactor $reactor) {
$unresolved = new Unresolved($reactor);
public function __construct() {
$unresolved = new Unresolved;
$resolver = function(\Exception $error = null, $result = null) {
$this->resolve($error, $result); // bound to private Unresolved::resolve()
};

View File

@ -41,12 +41,4 @@ interface Promise {
* @return self
*/
public function watch(callable $func);
/**
* Block script execution indefinitely until the promise resolves
*
* In the event of promise failure, implementations MUST throw the Exception object used to
* fail the Promise. Upon success this method MUST return the successfully resolved value.
*/
public function wait();
}

View File

@ -2,26 +2,19 @@
namespace Amp;
/**
* The event reactor is a truly global thing in single-threaded code. Applications should use
* a single reactor per thread. Accidentally using multiple reactors can lead to all manner of
* hard-to-debug problems. Should you almost always avoid static and singletons? Yes, and if you
* abuse this static factory method it's your fault. However, there is nothing wrong with
* asking for a Reactor instance in your code and using lazy injection via this method if it's
* not provided.
*
* DO NOT instantiate multiple event loops in your PHP application!
*/
class ReactorFactory {
private static $reactor;
/**
* Select a global event reactor based on the current environment
*
* @param callable $factory An optional factory callable to generate the shared reactor yourself
* @return \Amp\Reactor
* This method is deprecated. New code should use Amp\getReactor() instead.
*/
public static function select(callable $factory = null) {
trigger_error(
'Amp\\ReactorFactory is deprecated and scheduled for removal. ' .
'Please update code to use the Amp\\getReactor() function instead.',
E_USER_DEPRECATED
);
if (self::$reactor) {
return self::$reactor;
} elseif ($factory) {

View File

@ -39,14 +39,15 @@ class Success implements Promise {
}
/**
* Wait for Future value resolution
*
* NOTE: because this object represents a successfully resolved Promise it will *always* return
* the resolved result immediately.
*
* @return mixed
* This method is deprecated. New code should use Amp\wait($promise) instead.
*/
public function wait() {
trigger_error(
'Amp\\Promise::wait() is deprecated and scheduled for removal. ' .
'Please update code to use Amp\\wait($promise) instead.',
E_USER_DEPRECATED
);
return $this->result;
}
}

View File

@ -7,7 +7,6 @@ namespace Amp;
* the Promisor that created it.
*/
class Unresolved implements Promise {
private $reactor;
private $isWaiting = false;
private $isResolved = false;
private $watchers = [];
@ -15,13 +14,6 @@ class Unresolved implements Promise {
private $error;
private $result;
/**
* @param \Amp\Reactor $reactor
*/
public function __construct(Reactor $reactor) {
$this->reactor = $reactor;
}
/**
* Notify the $func callback when the promise resolves (whether successful or not)
*
@ -53,32 +45,26 @@ class Unresolved implements Promise {
}
/**
* Block script execution indefinitely until the promise resolves
*
* @throws \Exception
* @return mixed
* This method is deprecated. New code should use Amp\wait($promise) instead.
*/
public function wait() {
if ($this->error) {
throw $error;
} elseif ($this->isResolved) {
return $this->result;
}
trigger_error(
'Amp\\Promise::wait() is deprecated and scheduled for removal. ' .
'Please update code to use Amp\\wait($promise) instead.',
E_USER_DEPRECATED
);
$resolvedError;
$resolvedResult;
$this->whens[] = function($error, $result) use (&$resolvedError, &$resolvedResult) {
$isWaiting = true;
$resolvedError = $resolvedResult = null;
$this->when(function($error, $result) use (&$isWaiting, &$resolvedError, &$resolvedResult) {
$isWaiting = false;
$resolvedError = $error;
$resolvedResult = $result;
$this->isWaiting = false;
};
$this->isWaiting = true;
while ($this->isWaiting) {
$this->reactor->tick();
});
$reactor = getReactor();
while ($isWaiting) {
$reactor->tick();
}
if ($resolvedError) {
throw $resolvedError;
}

View File

@ -3,16 +3,218 @@
namespace Amp;
/**
* Get a singleton event reactor instance
* Get the global singleton event reactor instance
*
* Note that the $factory callable is only invoked if no global reactor has yet been initialized.
*
* @param callable $factory Optional factory callable for initializing a reactor
* @return \Amp\Reactor
* @param bool $forceNew If true return a new Reactor instance (but don't store it for future use)
* @return Reactor
*/
function reactor(callable $factory = null) {
function getReactor($forceNew = false) {
static $reactor;
return ($reactor = $reactor ?: ReactorFactory::select($factory));
if ($forceNew) {
return chooseReactor();
} elseif (empty($reactor)) {
return $reactor = chooseReactor();
} else {
return $reactor;
}
}
/**
* Select the most appropriate event reactor given the current execution environment
*
* @return LibeventReactor|NativeReactor|UvReactor
*/
function chooseReactor() {
if (extension_loaded('uv')) {
return new UvReactor;
} elseif (extension_loaded('libevent')) {
return new LibeventReactor;
} else {
return new NativeReactor;
}
}
/**
* Start an event reactor and assume program flow control
*
* @param callable $onStart Optional callback to invoke immediately upon reactor start
* @return void
*/
function run(callable $onStart = null) {
return getReactor()->run($onStart);
}
/**
* Execute a single event loop iteration
*
* @param bool $noWait
* @return void
*/
function tick($noWait = false) {
return getReactor()->tick($noWait);
}
/**
* Stop the event reactor
*
* @return void
*/
function stop() {
return getReactor()->stop();
}
/**
* Schedule a callback for immediate invocation in the next event loop iteration
*
* Watchers registered using this function will be automatically garbage collected after execution.
*
* @param callable $func Any valid PHP callable
* @return int Returns the unique watcher ID for disable/enable/cancel
*/
function immediately(callable $func) {
return getReactor()->immediately($func);
}
/**
* Schedule a callback to execute once
*
* Watchers registered using this function will be automatically garbage collected after execution.
*
* @param callable $func Any valid PHP callable
* @param int $msDelay The delay in milliseconds before the callback will trigger (may be zero)
* @return int Returns the unique watcher ID for disable/enable/cancel
*/
function once(callable $func, $msDelay) {
return getReactor()->once($func, $msDelay);
}
/**
* Schedule a recurring callback to execute every $interval seconds until cancelled
*
* IMPORTANT: Watchers registered using this function must be manually cleared using cancel() to
* free the associated memory. Failure to cancel repeating watchers (even if disable() is used)
* will lead to memory leaks.
*
* @param callable $func Any valid PHP callable
* @param int $msDelay The delay in milliseconds in-between callback invocations (may be zero)
* @return int Returns the unique watcher ID for disable/enable/cancel
*/
function repeat(callable $func, $msDelay) {
return getReactor()->repeat($func, $msDelay);
}
/**
* Schedule an event to trigger once at the specified time
*
* Watchers registered using this function will be automatically garbage collected after execution.
*
* @param callable $func Any valid PHP callable
* @param string $timeString Any string that can be parsed by strtotime() and is in the future
* @return int Returns the unique watcher ID for disable/enable/cancel
*/
function at(callable $func, $timeString) {
return getReactor()->at($func, $timeString);
}
/**
* Enable a disabled timer or stream IO watcher
*
* Calling enable() on an already-enabled watcher will have no effect.
*
* @param int $watcherId
* @return void
*/
function enable($watcherId) {
return getReactor()->enable($watcherId);
}
/**
* Temporarily disable (but don't cancel) an existing timer/stream watcher
*
* Calling disable() on a nonexistent or previously-disabled watcher will have no effect.
*
* NOTE: Disabling a repeating or stream watcher is not sufficient to free associated resources.
* When the watcher is no longer needed applications must still use cancel() to clear related
* memory and avoid leaks.
*
* @param int $watcherId
* @return void
*/
function disable($watcherId) {
return getReactor()->disable($watcherId);
}
/**
* Cancel an existing timer/stream watcher
*
* Calling cancel() on a non-existent watcher will have no effect.
*
* @param int $watcherId
* @return void
*/
function cancel($watcherId) {
return getReactor()->cancel($watcherId);
}
/**
* Watch a stream IO resource for readable data and trigger the specified callback when actionable
*
* IMPORTANT: Watchers registered using this function must be manually cleared using cancel() to
* free the associated memory. Failure to cancel repeating watchers (even if disable() is used)
* will lead to memory leaks.
*
* @param resource $stream A stream resource to watch for readable data
* @param callable $func Any valid PHP callable
* @param bool $enableNow Should the watcher be enabled now or held for later use?
* @return int Returns the unique watcher ID for disable/enable/cancel
*/
function onReadable($stream, callable $func, $enableNow = true) {
return getReactor()->onReadable($stream, $func, $enableNow);
}
/**
* Watch a stream IO resource for writability and trigger the specified callback when actionable
*
* NOTE: Sockets are essentially "always writable" (as long as their write buffer is not full).
* Therefore, it's critical that applications disable or cancel write watchers as soon as all data
* is written or the watcher will trigger endlessly and hammer the CPU.
*
* IMPORTANT: Watchers registered using this function must be manually cleared using cancel() to
* free the associated memory. Failure to cancel repeating watchers (even if disable() is used)
* will lead to memory leaks.
*
* @param resource $stream A stream resource to watch for writable data
* @param callable $func Any valid PHP callable
* @param bool $enableNow Should the watcher be enabled now or held for later use?
* @return int Returns the unique watcher ID for disable/enable/cancel
*/
function onWritable($stream, callable $func, $enableNow = true) {
return getReactor()->onWritable($stream, $func, $enableNow);
}
/**
* React to process control signals
*
* @param int $signo The signal number to watch for
* @param callable $onSignal
* @throws \RuntimeException if the current environment cannot support signal handling
* @return int Returns a unique integer watcher ID
*/
function onSignal($signo, callable $onSignal) {
/**
* @var $reactor \Amp\SignalReactor
*/
$reactor = getReactor();
if ($reactor instanceof SignalReactor) {
return $reactor->onSignal($signo, $onSignal);
} else {
throw new \RuntimeException(
'Your PHP environment does not support signal handling. Please install pecl/libevent or the php-uv extension'
);
}
}
/**
@ -20,18 +222,17 @@ function reactor(callable $factory = null) {
* the resulting Promise succeeds with an array matching keys from the input array
* to their resolved values.
*
* @param array[\Amp\Promise] $promises
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @param array[Promise] $promises
* @return Promise
*/
function all(array $promises, Reactor $reactor = null) {
function all(array $promises) {
if (empty($promises)) {
return new Success([]);
}
$results = [];
$remaining = count($promises);
$promisor = new Future($reactor ?: reactor());
$promisor = new Future;
$isResolved = false;
foreach ($promises as $key => $resolvable) {
@ -78,11 +279,10 @@ function all(array $promises, Reactor $reactor = null) {
* The individual keys in the resulting arrays are preserved from the initial Promise array
* passed to the function for evaluation.
*
* @param array[\Amp\Promise] $promises
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @param array[Promise] $promises
* @return Promise
*/
function some(array $promises, Reactor $reactor = null) {
function some(array $promises) {
if (empty($promises)) {
return new Failure(new \LogicException(
'No promises or values provided for resolution'
@ -91,7 +291,7 @@ function some(array $promises, Reactor $reactor = null) {
$results = $errors = [];
$remaining = count($promises);
$promisor = new Future($reactor ?: reactor());
$promisor = new Future;
foreach ($promises as $key => $resolvable) {
if (!$resolvable instanceof Promise) {
@ -130,11 +330,10 @@ function some(array $promises, Reactor $reactor = null) {
* This function is the same as some() with the notable exception that it will never fail even
* if all promises in the array resolve unsuccessfully.
*
* @param array $promises
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @param array[Promise] $promises
* @return Promise
*/
function any(array $promises, Reactor $reactor = null) {
function any(array $promises) {
if (empty($promises)) {
return new Success([], []);
}
@ -142,7 +341,7 @@ function any(array $promises, Reactor $reactor = null) {
$results = [];
$errors = [];
$remaining = count($promises);
$promisor = new Future($reactor ?: reactor());
$promisor = new Future;
foreach ($promises as $key => $resolvable) {
if (!$resolvable instanceof Promise) {
@ -173,11 +372,10 @@ function any(array $promises, Reactor $reactor = null) {
* Resolves with the first successful Promise value. The resulting Promise will only fail if all
* Promise values in the group fail or if the initial Promise array is empty.
*
* @param array[\Amp\Promise] $promises
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @param array[Promise] $promises
* @return Promise
*/
function first(array $promises, Reactor $reactor = null) {
function first(array $promises) {
if (empty($promises)) {
return new Failure(new \LogicException(
'No promises or values provided for resolution'
@ -186,7 +384,7 @@ function first(array $promises, Reactor $reactor = null) {
$remaining = count($promises);
$isComplete = false;
$promisor = new Future($reactor ?: reactor());
$promisor = new Future;
foreach ($promises as $resolvable) {
if (!$resolvable instanceof Promise) {
@ -217,19 +415,18 @@ function first(array $promises, Reactor $reactor = null) {
/**
* Map promised future values using the specified functor
*
* @param array $promises
* @param array[Promise] $promises
* @param callable $functor
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @return Promise
*/
function map(array $promises, callable $functor, Reactor $reactor = null) {
function map(array $promises, callable $functor) {
if (empty($promises)) {
return new Success([]);
}
$results = [];
$remaining = count($promises);
$promisor = new Future($reactor ?: reactor());
$promisor = new Future;
foreach ($promises as $key => $resolvable) {
$promise = ($resolvable instanceof Promise) ? $resolvable : new Success($resolvable);
@ -267,19 +464,18 @@ function map(array $promises, callable $functor, Reactor $reactor = null) {
* If the functor returns a truthy value the resolved promise result is retained, otherwise it is
* discarded. Array keys are retained for any results not filtered out by the functor.
*
* @param array $promises
* @param array[Promise] $promises
* @param callable $functor
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @return Promise
*/
function filter(array $promises, callable $functor, Reactor $reactor = null) {
function filter(array $promises, callable $functor) {
if (empty($promises)) {
return new Success([]);
}
$results = [];
$remaining = count($promises);
$promisor = new Future($reactor ?: reactor());
$promisor = new Future;
foreach ($promises as $key => $resolvable) {
$promise = ($resolvable instanceof Promise) ? $resolvable : new Success($resolvable);
@ -312,245 +508,73 @@ function filter(array $promises, callable $functor, Reactor $reactor = null) {
}
/**
* A co-routine to resolve Generators
* Block script execution indefinitely until the specified Promise resolves
*
* Returns a promise that resolves when the generator completes. The final value
* yielded by the generator is used to resolve the returned Promise on success.
* In the event of promise failure this method will throw the exception responsible for the failure.
* Otherwise the promise's resolved value is returned.
*
* Example:
* If the optional event reactor instance is not specified then the global default event reactor
* is used. Applications should be very careful to avoid instantiating multiple event reactors as
* this can lead to hard-to-debug failures. If the async value producer uses a different event
* reactor instance from that specified in this method the wait() call will never return.
*
* function anotherGenerator() {
* // wait 100 milliseconds before proceeding
* yield 'wait' => 100;
* yield 1;
* }
*
* $generator = function() {
* $a = (yield 2);
* $b = (yield new Success(21));
* $c = (yield anotherGenerator());
* yield $a * $b * $c;
* };
*
* $result = resolve($generator())->wait();
* var_dump($result); // int(42)
*
* @param \Generator $gen
* @param \Amp\Reactor $reactor
* @return \Amp\Promise
* @param Promise $promise A promise on which to wait for resolution
* @param Reactor $reactor An optional event reactor instance
* @throws \Exception
* @return mixed
*/
function resolve(\Generator $gen, Reactor $reactor = null) {
$promisor = new Future($reactor ?: reactor());
__advanceGenerator($reactor, $gen, $promisor);
function wait(Promise $promise, Reactor $reactor = null) {
$isWaiting = true;
$resolvedError = null;
$resolvedResult = null;
return $promisor;
}
$promise->when(function($error, $result) use (&$isWaiting, &$resolvedError, &$resolvedResult) {
$isWaiting = false;
$resolvedError = $error;
$resolvedResult = $result;
});
function __advanceGenerator(Reactor $reactor, \Generator $gen, Promisor $promisor, $previous = null) {
try {
if ($gen->valid()) {
$key = $gen->key();
$current = $gen->current();
$promiseStruct = __promisifyGeneratorYield($reactor, $key, $current);
$reactor->immediately(function() use ($reactor, $gen, $promisor, $promiseStruct) {
list($promise, $noWait) = $promiseStruct;
if ($noWait) {
__sendToGenerator($reactor, $gen, $promisor, $error = null, $result = null);
} else {
$promise->when(function($error, $result) use ($reactor, $gen, $promisor) {
__sendToGenerator($reactor, $gen, $promisor, $error, $result);
});
}
});
} else {
$promisor->succeed($previous);
}
} catch (\Exception $error) {
$promisor->fail($error);
$reactor = $reactor ?: getReactor();
while ($isWaiting) {
$reactor->tick();
}
if ($resolvedError) {
throw $resolvedError;
}
return $resolvedResult;
}
function __promisifyGeneratorYield(Reactor $reactor, $key, $current) {
$noWait = false;
// === DEPRECATED FUNCTIONS ========================================================================
if ($key === (string) $key) {
goto explicit_key;
/**
* Get the global singleton event reactor instance
*
* Note that the $factory callable is only invoked if no global reactor has yet been initialized.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* === THIS FUNCTION IS DEPRECATED ===
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* @param callable $factory Optional factory callable for initializing a reactor
* @return Reactor
*/
function reactor(callable $factory = null) {
static $reactor;
$msg = 'This function is deprecated and scheduled for removal. Please use Amp\\getReactor()';
trigger_error($msg, E_USER_DEPRECATED);
if ($reactor) {
return $reactor;
} elseif ($factory) {
return ($reactor = $factory());
} elseif (extension_loaded('uv')) {
return ($reactor = new UvReactor);
} elseif (extension_loaded('libevent')) {
return ($reactor = new LibeventReactor);
} else {
goto implicit_key;
}
explicit_key: {
$key = strtolower($key);
if ($key[0] === YieldCommands::NOWAIT_PREFIX) {
$noWait = true;
$key = substr($key, 1);
}
switch ($key) {
case YieldCommands::ALL:
// fallthrough
case YieldCommands::ANY:
// fallthrough
case YieldCommands::SOME:
if (is_array($current)) {
goto combinator;
} else {
$promise = new Failure(new \DomainException(
sprintf('"%s" yield command expects array; %s yielded', $key, gettype($current))
));
goto return_struct;
}
case YieldCommands::WAIT:
goto wait;
case YieldCommands::IMMEDIATELY:
goto immediately;
case YieldCommands::ONCE:
// fallthrough
case YieldCommands::REPEAT:
goto schedule;
case YieldCommands::ON_READABLE:
$ioWatchMethod = 'onReadable';
goto stream_io_watcher;
case YieldCommands::ON_WRITABLE:
$ioWatchMethod = 'onWritable';
goto stream_io_watcher;
case YieldCommands::ENABLE:
// fallthrough
case YieldCommands::DISABLE:
// fallthrough
case YieldCommands::CANCEL:
goto watcher_control;
case YieldCommands::NOWAIT:
$noWait = true;
goto implicit_key;
default:
$promise = new Failure(new \DomainException(
sprintf('Unknown or invalid yield key: "%s"', $key)
));
goto return_struct;
}
}
implicit_key: {
if ($current instanceof Promise) {
$promise = $current;
} elseif ($current instanceof \Generator) {
$promise = resolve($reactor, $current);
} elseif (is_array($current)) {
$key = YieldCommands::ALL;
goto combinator;
} else {
$promise = new Success($current);
}
goto return_struct;
}
combinator: {
$promises = [];
foreach ($current as $index => $element) {
if ($element instanceof Promise) {
$promise = $element;
} elseif ($element instanceof \Generator) {
$promise = resolve($element, $reactor);
} else {
$promise = new Success($element);
}
$promises[$index] = $promise;
}
$combinatorFunction = __NAMESPACE__ . "\\{$key}";
$promise = $combinatorFunction($promises, $reactor);
goto return_struct;
}
immediately: {
if (is_callable($current)) {
$watcherId = $reactor->immediately($current);
$promise = new Success($watcherId);
} else {
$promise = new Failure(new \DomainException(
sprintf(
'"%s" yield command requires callable; %s provided',
$key,
gettype($current)
)
));
}
goto return_struct;
}
schedule: {
if (is_array($current) && isset($current[0], $current[1]) && is_callable($current[0])) {
list($func, $msDelay) = $current;
$watcherId = $reactor->{$key}($func, $msDelay);
$promise = new Success($watcherId);
} else {
$promise = new Failure(new \DomainException(
sprintf(
'"%s" yield command requires [callable $func, int $msDelay]; %s provided',
$key,
gettype($current)
)
));
}
goto return_struct;
}
stream_io_watcher: {
if (is_array($current) && isset($current[0], $current[1]) && is_callable($current[1])) {
list($stream, $func, $enableNow) = $current;
$watcherId = $reactor->{$ioWatchMethod}($stream, $func, $enableNow);
$promise = new Success($watcherId);
} else {
$promise = new Failure(new \DomainException(
sprintf(
'"%s" yield command requires [resource $stream, callable $func, bool $enableNow]; %s provided',
$key,
gettype($current)
)
));
}
goto return_struct;
}
wait: {
$promisor = new Future($reactor);
$reactor->once(function() use ($promisor) {
$promisor->succeed();
}, (int) $current);
$promise = $promisor;
goto return_struct;
}
watcher_control: {
$reactor->{$key}($current);
$promise = new Success;
goto return_struct;
}
return_struct: {
return [$promise, $noWait];
}
}
function __sendToGenerator(Reactor $reactor, \Generator $gen, Promisor $promisor, \Exception $error = null, $result = null) {
try {
if ($error) {
$gen->throw($error);
} else {
$gen->send($result);
}
__advanceGenerator($reactor, $gen, $promisor, $result);
} catch (\Exception $error) {
$promisor->fail($error);
return ($reactor = new NativeReactor);
}
}

View File

@ -14,14 +14,4 @@ class FailureTest extends \PHPUnit_Framework_TestCase {
$this->assertSame($exception, $error);
});
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage test
*/
public function testWaitThrowsImmediately() {
$exception = new \RuntimeException('test');
$failure = new Failure($exception);
$failure->wait();
}
}

View File

@ -1,311 +0,0 @@
<?php
namespace Amp\Test;
use Amp\Success;
use Amp\Failure;
use Amp\NativeReactor;
class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testAllResolvesWithArrayOfResults() {
$promises = [
'r1' => new Success(42),
'r2' => new Success(41),
];
$reactor = new NativeReactor;
$expected = ['r1' => 42, 'r2' => 41];
$results = \Amp\all($promises, $reactor)->wait();
$this->assertSame($expected, $results);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage zanzibar
*/
public function testAllThrowsIfAnyIndividualPromiseFails() {
$exception = new \RuntimeException('zanzibar');
$promises = [
'r1' => new Success(42),
'r2' => new Failure($exception),
'r3' => new Success(40),
];
$reactor = new NativeReactor;
$results = \Amp\all($promises, $reactor)->wait();
}
public function testSomeReturnsArrayOfErrorsAndResults() {
$exception = new \RuntimeException('zanzibar');
$promises = [
'r1' => new Success(42),
'r2' => new Failure($exception),
'r3' => new Success(40),
];
$reactor = new NativeReactor;
list($errors, $results) = \Amp\some($promises, $reactor)->wait();
$this->assertSame(['r2' => $exception], $errors);
$this->assertSame(['r1' => 42, 'r3' => 40], $results);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage All promises failed
*/
public function testSomeThrowsIfNoPromisesResolveSuccessfully() {
$promises = [
'r1' => new Failure(new \RuntimeException),
'r2' => new Failure(new \RuntimeException),
];
$reactor = new NativeReactor;
list($errors, $results) = \Amp\some($promises, $reactor)->wait();
}
public function testResolveResolvesGeneratorResult() {
$gen = function() {
$a = (yield 21);
$b = (yield new Success(2));
yield ($a * $b);
};
$reactor = new NativeReactor;
$promise = \Amp\resolve($gen(), $reactor);
$expected = 42;
$actual = $promise->wait();
$this->assertSame($expected, $actual);
}
// --- resolve() tests ------------------------------ //
public function testResolve() {
$gen = function() { yield 42; };
$reactor = new NativeReactor;
$result = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(42, $result);
}
public function testResolvedValueEqualsFinalYield() {
$gen = function() {
$a = (yield 21);
$b = (yield new Success(2));
yield ($a * $b);
};
$expected = 42;
$reactor = new NativeReactor;
$actual = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame($expected, $actual);
}
public function testFutureErrorsAreThrownIntoGenerator() {
$gen = function() {
$a = (yield 21);
$b = 1;
try {
yield new Failure(new \Exception('test'));
$this->fail('Code path should not be reached');
} catch (\Exception $e) {
$this->assertSame('test', $e->getMessage());
$b = 2;
}
yield ($a * $b);
};
$expected = 42;
$reactor = new NativeReactor;
$actual = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame($expected, $actual);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage When in the chronicle of wasted time
*/
public function testUncaughtGeneratorExceptionFailsResolverPromise() {
$gen = function() {
yield;
throw new \Exception('When in the chronicle of wasted time');
yield;
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
}
public function testImplicitAllCombinatorResolution() {
$gen = function() {
list($a, $b) = (yield [new Success(21), new Success(2)]);
yield ($a * $b);
};
$reactor = new NativeReactor;
$result = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(42, $result);
}
public function testImplicitAllCombinatorResolutionWithNonPromises() {
$gen = function() {
list($a, $b, $c) = (yield [new Success(21), new Success(2), 10]);
yield ($a * $b * $c);
};
$reactor = new NativeReactor;
$result = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(420, $result);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage When in the chronicle of wasted time
*/
public function testImplicitCombinatorResolutionThrowsIfAnyOnePromiseFails() {
$gen = function() {
list($a, $b) = (yield [
new Success(21),
new Failure(new \Exception('When in the chronicle of wasted time')),
]);
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
}
public function testImplicitCombinatorResolvesGeneratorInArray() {
$gen1 = function() {
yield 21;
};
$gen2 = function() use ($gen1) {
list($a, $b) = (yield [
$gen1(),
new Success(2)
]);
yield ($a * $b);
};
$reactor = new NativeReactor;
$result = \Amp\resolve($gen2(), $reactor)->wait();
$this->assertSame(42, $result);
}
public function testExplicitAllCombinatorResolution() {
$gen = function() {
list($a, $b, $c) = (yield 'all' => [
new Success(21),
new Success(2),
10
]);
yield ($a * $b * $c);
};
$reactor = new NativeReactor;
$result = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(420, $result);
}
public function testExplicitAnyCombinatorResolution() {
$gen = function() {
yield 'any' => [
'a' => new Success(21),
'b' => new Failure(new \Exception('test')),
];
};
$reactor = new NativeReactor;
list($errors, $results) = \Amp\resolve($gen(), $reactor)->wait();
$this->assertSame('test', $errors['b']->getMessage());
$this->assertSame(21, $results['a']);
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage All promises failed
*/
public function testExplicitSomeCombinatorResolutionFailsOnError() {
$gen = function() {
yield 'some' => [
'r1' => new Failure(new \RuntimeException),
'r2' => new Failure(new \RuntimeException),
];
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
}
/**
* @expectedException \DomainException
* @expectedExceptionMessage "some" yield command expects array; string yielded
*/
public function testExplicitCombinatorResolutionFailsIfNonArrayYielded() {
$gen = function() {
yield 'some' => 'test';
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
}
public function testExplicitImmediatelyYieldResolution() {
$var = null;
$gen = function() use (&$var) {
yield 'immediately' => function() use (&$var) {
$var = 42;
};
yield 'wait' => 100; // wait 100ms so the immediately callback executes
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(42, $var);
}
public function testExplicitOnceYieldResolution() {
$var = null;
$gen = function() use (&$var) {
yield 'once' => [function() use (&$var) { $var = 42; }, $msDelay=1];
yield 'wait' => 100; // wait 100ms so the once callback executes
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(42, $var);
}
public function testExplicitRepeatYieldResolution() {
$var = 1;
$repeatFunc = function($reactor, $watcherId) use (&$var) {
yield 'cancel' => $watcherId;
$var++;
};
$gen = function() use (&$var, $repeatFunc) {
yield 'repeat' => [$repeatFunc, $msDelay = 1];
yield 'wait' => 100; // wait 100ms so we can be sure the repeat callback executes
};
$reactor = new NativeReactor;
\Amp\resolve($gen(), $reactor)->wait();
$this->assertSame(2, $var);
}
}

View File

@ -7,14 +7,14 @@ use Amp\NativeReactor;
class FutureTest extends \PHPUnit_Framework_TestCase {
public function testPromiseReturnsSelf() {
$future = new Future($this->getMock('Amp\Reactor'));
$this->assertSame($future, $future->promise());
$promisor = new Future;
$this->assertSame($promisor, $promisor->promise());
}
public function testWhenInvokesCallbackWithResultIfAlreadySucceeded() {
$deferred = new Future($this->getMock('Amp\Reactor'));
$promise = $deferred->promise();
$deferred->succeed(42);
$promisor = new Future;
$promise = $promisor->promise();
$promisor->succeed(42);
$promise->when(function($e, $r) {
$this->assertSame(42, $r);
$this->assertNull($e);
@ -22,7 +22,7 @@ class FutureTest extends \PHPUnit_Framework_TestCase {
}
public function testWhenInvokesCallbackWithErrorIfAlreadyFailed() {
$promisor = new Future($this->getMock('Amp\Reactor'));
$promisor = new Future;
$promise = $promisor->promise();
$exception = new \Exception('test');
$promisor->fail($exception);
@ -32,19 +32,12 @@ class FutureTest extends \PHPUnit_Framework_TestCase {
});
}
public function testWaitReturnsOnResolution() {
$reactor = new NativeReactor;
$promisor = new Future($reactor);
$reactor->once(function() use ($promisor) { $promisor->succeed(42); }, $msDelay = 100);
$this->assertSame(42, $promisor->promise()->wait());
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Promise already resolved
*/
public function testSucceedThrowsIfAlreadyResolved() {
$promisor = new Future($this->getMock('Amp\Reactor'));
$promisor = new Future;
$promisor->succeed(42);
$promisor->succeed('zanzibar');
}
@ -54,7 +47,7 @@ class FutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage A Promise cannot act as its own resolution result
*/
public function testSucceedThrowsIfPromiseIsTheResolutionValue() {
$promisor = new Future($this->getMock('Amp\Reactor'));
$promisor = new Future;
$promise = $promisor->promise();
$promisor->succeed($promise);
}
@ -64,23 +57,20 @@ class FutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage Promise already resolved
*/
public function testFailThrowsIfAlreadyResolved() {
$promisor = new Future($this->getMock('Amp\Reactor'));
$promisor = new Future;
$promisor->succeed(42);
$promisor->fail(new \Exception);
}
public function testSucceedingWithPromisePipelinesResult() {
$reactor = new NativeReactor;
$promisor = new Future($reactor);
$next = new Future($reactor);
$reactor->once(function() use ($next) {
$next->succeed(42);
}, $msDelay = 1);
$promisor->succeed($next->promise());
$this->assertSame(42, $promisor->promise()->wait());
(new NativeReactor)->run(function() {
$next = new Future;
$promisor = new Future;
$promisor->succeed($next->promise());
yield 'once' => [function() use ($next) { $next->succeed(42); }, $msDelay = 10];
$result = (yield $promisor->promise());
$this->assertSame(42, $result);
});
}
/**
@ -88,15 +78,14 @@ class FutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage fugazi
*/
public function testFailingWithPromisePipelinesResult() {
$reactor = new NativeReactor;
$promisor = new Future($reactor);
$next = new Future($reactor);
(new NativeReactor)->run(function() {
$promisor = new Future;
$next = new Future;
$once = function() use ($next) { $next->fail(new \RuntimeException('fugazi')); };
yield 'once' => [$once, $msDelay = 10];
$promisor->succeed($next->promise());
$reactor->once(function() use ($next) {
$next->fail(new \RuntimeException('fugazi'));
}, $msDelay = 10);
$promisor->succeed($next->promise());
$promisor->promise()->wait();
yield $promisor->promise();
});
}
}

View File

@ -0,0 +1,285 @@
<?php
namespace Amp\Test;
use Amp\Success;
use Amp\Failure;
use Amp\NativeReactor;
class GeneratorResolverTest extends \PHPUnit_Framework_TestCase {
public function testAllResolvesWithArrayOfResults() {
(new NativeReactor)->run(function($reactor) {
$expected = ['r1' => 42, 'r2' => 41];
$actual = (yield 'all' => [
'r1' => 42,
'r2' => new Success(41),
]);
$this->assertSame($expected, $actual);
});
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage zanzibar
*/
public function testAllThrowsIfAnyIndividualPromiseFails() {
(new NativeReactor)->run(function($reactor) {
$exception = new \RuntimeException('zanzibar');
$promises = [
'r1' => new Success(42),
'r2' => new Failure($exception),
'r3' => new Success(40),
];
$results = (yield $promises);
});
}
public function testSomeReturnsArrayOfErrorsAndResults() {
(new NativeReactor)->run(function($reactor) {
$exception = new \RuntimeException('zanzibar');
$promises = [
'r1' => new Success(42),
'r2' => new Failure($exception),
'r3' => new Success(40),
];
list($errors, $results) = (yield 'some' => $promises);
$this->assertSame(['r2' => $exception], $errors);
$this->assertSame(['r1' => 42, 'r3' => 40], $results);
});
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage All promises failed
*/
public function testSomeThrowsIfNoPromisesResolveSuccessfully() {
(new NativeReactor)->run(function($reactor) {
$promises = [
'r1' => new Failure(new \RuntimeException),
'r2' => new Failure(new \RuntimeException),
];
list($errors, $results) = (yield 'some' => $promises);
});
}
public function testResolvedValueEqualsFinalYield() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
$a = (yield 21);
$b = (yield new Success(2));
yield ($a * $b);
};
$result = (yield $gen());
$this->assertSame(42, $result);
});
}
public function testFutureErrorsAreThrownIntoGenerator() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
$a = (yield 21);
$b = 1;
try {
yield new Failure(new \Exception('test'));
$this->fail('Code path should not be reached');
} catch (\Exception $e) {
$this->assertSame('test', $e->getMessage());
$b = 2;
}
yield ($a * $b);
};
$result = (yield $gen());
$this->assertSame(42, $result);
});
}
/**
* @expectedException \Exception
* @expectedExceptionMessage When in the chronicle of wasted time
*/
public function testUncaughtGeneratorExceptionFailsResolverPromise() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
yield;
throw new \Exception('When in the chronicle of wasted time');
yield;
};
yield $gen();
});
}
public function testImplicitAllCombinatorResolution() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
list($a, $b) = (yield [
new Success(21),
new Success(2),
]);
yield ($a * $b);
};
$result = (yield $gen());
$this->assertSame(42, $result);
});
}
public function testImplicitAllCombinatorResolutionWithNonPromises() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
list($a, $b, $c) = (yield [new Success(21), new Success(2), 10]);
yield ($a * $b * $c);
};
$result = (yield $gen());
$this->assertSame(420, $result);
});
}
/**
* @expectedException \Exception
* @expectedExceptionMessage When in the chronicle of wasted time
*/
public function testImplicitAllCombinatorResolutionThrowsIfAnyOnePromiseFails() {
$gen = function() {
list($a, $b) = (yield [
new Success(21),
new Failure(new \Exception('When in the chronicle of wasted time')),
]);
};
$reactor = new NativeReactor;
$reactor->run(function($reactor) use ($gen) {
yield $gen();
});
}
public function testImplicitCombinatorResolvesGeneratorInArray() {
(new NativeReactor)->run(function($reactor) {
$gen1 = function() {
yield 21;
};
$gen2 = function() use ($gen1) {
list($a, $b) = (yield [
$gen1(),
new Success(2)
]);
yield ($a * $b);
};
$result = (yield $gen2());
$this->assertSame(42, $result);
});
}
public function testExplicitAllCombinatorResolution() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
list($a, $b, $c) = (yield 'all' => [
new Success(21),
new Success(2),
10
]);
yield ($a * $b * $c);
};
$result = (yield $gen());
$this->assertSame(420, $result);
});
}
public function testExplicitAnyCombinatorResolution() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
yield 'any' => [
'a' => new Success(21),
'b' => new Failure(new \Exception('test')),
];
};
list($errors, $results) = (yield $gen());
$this->assertSame('test', $errors['b']->getMessage());
$this->assertSame(21, $results['a']);
});
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage All promises failed
*/
public function testExplicitSomeCombinatorResolutionFailsOnError() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
yield 'some' => [
'r1' => new Failure(new \RuntimeException),
'r2' => new Failure(new \RuntimeException),
];
};
yield $gen();
});
}
/**
* @expectedException \DomainException
* @expectedExceptionMessage "some" yield command expects array; string yielded
*/
public function testExplicitCombinatorResolutionFailsIfNonArrayYielded() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
yield 'some' => 'test';
};
yield $gen();
});
}
public function testExplicitImmediatelyYieldResolution() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
$var = null;
yield 'immediately' => function() use (&$var) { $var = 42; };
yield 'wait' => 100; // wait 100ms so the immediately callback executes
yield $var;
};
$result = (yield $gen());
$this->assertSame(42, $result);
});
}
public function testExplicitOnceYieldResolution() {
(new NativeReactor)->run(function($reactor) {
$gen = function() {
$var = null;
yield 'once' => [function() use (&$var) { $var = 42; }, $msDelay = 1];
yield 'wait' => 100; // wait 100ms so the once callback executes
yield $var;
};
$result = (yield $gen());
$this->assertSame(42, $result);
});
}
public function testExplicitRepeatYieldResolution() {
(new NativeReactor)->run(function($reactor) {
$var = null;
$repeatFunc = function($reactor, $watcherId) use (&$var) {
$var = 1;
yield 'cancel' => $watcherId;
$var++;
};
$gen = function() use (&$var, $repeatFunc) {
yield 'repeat' => [$repeatFunc, $msDelay = 1];
yield 'wait' => 100; // wait 100ms so we can be sure the repeat callback executes
yield $var;
};
$result = (yield $gen());
$this->assertSame(2, $result);
});
}
}

View File

@ -7,8 +7,8 @@ use Amp\NativeReactor;
class PrivateFutureTest extends \PHPUnit_Framework_TestCase {
public function testPromiseReturnsUnresolvedInstance() {
$future = new PrivateFuture($this->getMock('Amp\Reactor'));
$this->assertInstanceOf('Amp\Unresolved', $future->promise());
$promisor = new PrivateFuture;
$this->assertInstanceOf('Amp\Unresolved', $promisor->promise());
}
/**
@ -16,7 +16,7 @@ class PrivateFutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage Promise already resolved
*/
public function testSucceedThrowsIfAlreadyResolved() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promisor->succeed(42);
$promisor->succeed('zanzibar');
}
@ -26,7 +26,7 @@ class PrivateFutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage A Promise cannot act as its own resolution result
*/
public function testSucceedThrowsIfPromiseIsTheResolutionValue() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promise = $promisor->promise();
$promisor->succeed($promise);
}
@ -36,23 +36,22 @@ class PrivateFutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage Promise already resolved
*/
public function testFailThrowsIfAlreadyResolved() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promisor->succeed(42);
$promisor->fail(new \Exception);
}
public function testSucceedingWithPromisePipelinesResult() {
$reactor = new NativeReactor;
$promisor = new PrivateFuture($reactor);
$next = new PrivateFuture($reactor);
$reactor->once(function() use ($next) {
$next->succeed(42);
}, $msDelay = 1);
$promisor->succeed($next->promise());
$this->assertSame(42, $promisor->promise()->wait());
(new NativeReactor)->run(function($reactor) {
$promisor = new PrivateFuture;
$next = new PrivateFuture;
$reactor->once(function() use ($next) {
$next->succeed(42);
}, $msDelay = 1);
$promisor->succeed($next->promise());
$result = (yield $promisor->promise());
$this->assertSame(42, $result);
});
}
/**
@ -60,15 +59,17 @@ class PrivateFutureTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage fugazi
*/
public function testFailingWithPromisePipelinesResult() {
$reactor = new NativeReactor;
$promisor = new PrivateFuture($reactor);
$next = new PrivateFuture($reactor);
(new NativeReactor)->run(function($reactor) {
$promisor = new PrivateFuture;
$next = new PrivateFuture;
$reactor->once(function() use ($next) {
$next->fail(new \RuntimeException('fugazi'));
}, $msDelay = 10);
$reactor->once(function() use ($next) {
$next->fail(new \RuntimeException('fugazi'));
}, $msDelay = 10);
$promisor->succeed($next->promise());
$promisor->promise()->wait();
$promisor->succeed($next->promise());
yield $promisor->promise();
});
}
}

View File

@ -321,4 +321,12 @@ abstract class ReactorTest extends \PHPUnit_Framework_TestCase {
$reactor->run();
$this->assertSame("Art is the supreme task", $test);
}
public function testOnErrorCallbackInterceptsUncaughtException() {
$var = null;
$reactor = $this->getReactor();
$reactor->onError(function($e) use (&$var) { $var = $e->getMessage(); });
$reactor->run(function() { throw new \Exception('test'); });
$this->assertSame('test', $var);
}
}

View File

@ -14,10 +14,4 @@ class SuccessTest extends \PHPUnit_Framework_TestCase {
$this->assertSame($value, $result);
});
}
public function testWaitReturnsResolvedValue() {
$value = 42;
$success = new Success($value);
$this->assertSame($value, $success->wait());
}
}

View File

@ -8,9 +8,9 @@ use Amp\NativeReactor;
class UnresolvedTest extends \PHPUnit_Framework_TestCase {
public function testWatchInvokesCallbackWithResultIfAlreadySucceeded() {
$deferred = new PrivateFuture($this->getMock('Amp\Reactor'));
$promise = $deferred->promise();
$deferred->succeed(42);
$promisor = new PrivateFuture;
$promise = $promisor->promise();
$promisor->succeed(42);
$promise->watch(function($p, $e, $r) {
$this->assertSame(42, $r);
$this->assertNull($p);
@ -19,7 +19,7 @@ class UnresolvedTest extends \PHPUnit_Framework_TestCase {
}
public function testWatchInvokesCallbackWithErrorIfAlreadyFailed() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promise = $promisor->promise();
$exception = new \Exception('test');
$promisor->fail($exception);
@ -30,19 +30,12 @@ class UnresolvedTest extends \PHPUnit_Framework_TestCase {
});
}
public function testWaitReturnsOnResolution() {
$reactor = new NativeReactor;
$promisor = new PrivateFuture($reactor);
$reactor->once(function() use ($promisor) { $promisor->succeed(42); }, $msDelay = 100);
$this->assertSame(42, $promisor->promise()->wait());
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Promise already resolved
*/
public function testSucceedThrowsIfAlreadyResolved() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promisor->succeed(42);
$promisor->succeed('zanzibar');
}
@ -52,7 +45,7 @@ class UnresolvedTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage A Promise cannot act as its own resolution result
*/
public function testSucceedThrowsIfPromiseIsTheResolutionValue() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promise = $promisor->promise();
$promisor->succeed($promise);
}
@ -62,23 +55,24 @@ class UnresolvedTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage Promise already resolved
*/
public function testFailThrowsIfAlreadyResolved() {
$promisor = new PrivateFuture($this->getMock('Amp\Reactor'));
$promisor = new PrivateFuture;
$promisor->succeed(42);
$promisor->fail(new \Exception);
}
public function testSucceedingWithPromisePipelinesResult() {
$reactor = new NativeReactor;
$promisor = new PrivateFuture($reactor);
$next = new Future($reactor);
(new NativeReactor)->run(function($reactor) {
$promisor = new PrivateFuture;
$next = new Future;
$reactor->once(function() use ($next) {
$next->succeed(42);
}, $msDelay = 1);
$reactor->once(function() use ($next) {
$next->succeed(42);
}, $msDelay = 1);
$promisor->succeed($next->promise());
$promisor->succeed($next->promise());
$this->assertSame(42, $promisor->promise()->wait());
$this->assertSame(42, (yield $promisor->promise()));
});
}
/**
@ -86,15 +80,16 @@ class UnresolvedTest extends \PHPUnit_Framework_TestCase {
* @expectedExceptionMessage fugazi
*/
public function testFailingWithPromisePipelinesResult() {
$reactor = new NativeReactor;
$promisor = new PrivateFuture($reactor);
$next = new Future($reactor);
(new NativeReactor)->run(function($reactor) {
$promisor = new PrivateFuture;
$next = new Future;
$reactor->once(function() use ($next) {
$next->fail(new \RuntimeException('fugazi'));
}, $msDelay = 10);
$reactor->once(function() use ($next) {
$next->fail(new \RuntimeException('fugazi'));
}, $msDelay = 10);
$promisor->succeed($next->promise());
$promisor->promise()->wait();
$promisor->succeed($next->promise());
yield $promisor->promise();
});
}
}