function testRace() { $promise1 = new Promise(); $promise2 = new Promise(); $finalValue = 0; Promise\race([$promise1, $promise2])->then(function ($value) use(&$finalValue) { $finalValue = $value; }, function ($value) use(&$finalValue) { $finalValue = $value; }); $promise1->fulfill(1); Loop\run(); $this->assertEquals(1, $finalValue); $promise2->fulfill(2); Loop\run(); $this->assertEquals(1, $finalValue); }
/** * This method is used to call either an onFulfilled or onRejected callback. * * This method makes sure that the result of these callbacks are handled * correctly, and any chained promises are also correctly fulfilled or * rejected. * * @param Promise $subPromise * @param callable $callBack * @return void */ private function invokeCallback(Promise $subPromise, callable $callBack = null) { // We use 'nextTick' to ensure that the event handlers are always // triggered outside of the calling stack in which they were originally // passed to 'then'. // // This makes the order of execution more predictable. Loop\nextTick(function () use($callBack, $subPromise) { if (is_callable($callBack)) { try { $result = $callBack($this->value); if ($result instanceof self) { // If the callback (onRejected or onFulfilled) // returned a promise, we only fulfill or reject the // chained promise once that promise has also been // resolved. $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']); } else { // If the callback returned any other value, we // immediately fulfill the chained promise. $subPromise->fulfill($result); } } catch (Exception $e) { // If the event handler threw an exception, we need to make sure that // the chained promise is rejected as well. $subPromise->reject($e); } } else { if ($this->state === self::FULFILLED) { $subPromise->fulfill($this->value); } else { $subPromise->reject($this->value); } } }); }
$promise->fulfill($value . ", how are ya?"); }, 2); return $promise; })->then(function ($value) { echo "Step 4\n"; // This is the final event handler. return $value . " you rock!"; })->wait(); echo $result, "\n"; /* Now an identical example, this time with coroutines. */ $result = coroutine(function () { $promise = new Promise(); /* After 2 seconds we fulfill it */ Loop\setTimeout(function () use($promise) { echo "Step 1\n"; $promise->fulfill("hello"); }, 2); $value = (yield $promise); echo "Step 2\n"; $value .= ' world'; echo "Step 3\n"; $promise = new Promise(); Loop\setTimeout(function () use($promise, $value) { $promise->fulfill($value . ", how are ya?"); }, 2); $value = (yield $promise); echo "Step 4\n"; // This is the final event handler. (yield $value . " you rock!"); })->wait(); echo $result, "\n";
/** * Returns a Promise that resolves with the given value. * * If the value is a promise, the returned promise will attach itself to that * promise and eventually get the same state as the followed promise. * * @param mixed $value * @return Promise */ function resolve($value) { if ($value instanceof Promise) { return $value->then(); } else { $promise = new Promise(); $promise->fulfill($value); return $promise; } }
function testWaitResolve() { $promise = new Promise(); Loop\nextTick(function () use($promise) { $promise->fulfill(1); }); $this->assertEquals(1, $promise->wait()); }
/** * This method is used to call either an onFulfilled or onRejected callback. * * This method makes sure that the result of these callbacks are handled * correctly, and any chained promises are also correctly fulfilled or * rejected. * * @param Promise $subPromise * @param callable $callBack * @return void */ protected function invokeCallback(Promise $subPromise, callable $callBack = null) { if (is_callable($callBack)) { try { $result = $callBack($this->value); if ($result instanceof self) { $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']); } else { $subPromise->fulfill($result); } } catch (Exception $e) { $subPromise->reject($e); } } else { if ($this->state === self::FULFILLED) { $subPromise->fulfill($this->value); } else { $subPromise->reject($this->value); } } }
/** * Turn asynchronous promise-based code into something that looks synchronous * again, through the use of generators. * * Example without coroutines: * * $promise = $httpClient->request('GET', '/foo'); * $promise->then(function($value) { * * return $httpClient->request('DELETE','/foo'); * * })->then(function($value) { * * return $httpClient->request('PUT', '/foo'); * * })->error(function($reason) { * * echo "Failed because: $reason\n"; * * }); * * Example with coroutines: * * coroutine(function() { * * try { * yield $httpClient->request('GET', '/foo'); * yield $httpClient->request('DELETE', /foo'); * yield $httpClient->request('PUT', '/foo'); * } catch(\Exception $reason) { * echo "Failed because: $reason\n"; * } * * }); * * @copyright Copyright (C) 2013-2015 fruux GmbH. All rights reserved. * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ function coroutine(callable $gen) { $generator = $gen(); if (!$generator instanceof Generator) { throw new \InvalidArgumentException('You must pass a generator function'); } // This is the value we're returning. $promise = new Promise(); $lastYieldResult = null; /** * So tempted to use the mythical y-combinator here, but it's not needed in * PHP. */ $advanceGenerator = function () use(&$advanceGenerator, $generator, $promise, &$lastYieldResult) { while ($generator->valid()) { $yieldedValue = $generator->current(); if ($yieldedValue instanceof Promise) { $yieldedValue->then(function ($value) use($generator, &$advanceGenerator, &$lastYieldResult) { $lastYieldResult = $value; $generator->send($value); $advanceGenerator(); }, function ($reason) use($generator, $advanceGenerator) { if ($reason instanceof Exception) { $generator->throw($reason); } elseif (is_scalar($reason)) { $generator->throw(new Exception($reason)); } else { $type = is_object($reason) ? get_class($reason) : gettype($reason); $generator->throw(new Exception('Promise was rejected with reason of type: ' . $type)); } $advanceGenerator(); })->error(function ($reason) use($promise) { // This error handler would be called, if something in the // generator throws an exception, and it's not caught // locally. $promise->reject($reason); }); // We need to break out of the loop, because $advanceGenerator // will be called asynchronously when the promise has a result. break; } else { // If the value was not a promise, we'll just let it pass through. $lastYieldResult = $yieldedValue; $generator->send($yieldedValue); } } // If the generator is at the end, and we didn't run into an exception, // we can fullfill the promise with the last thing that was yielded to // us. if (!$generator->valid() && $promise->state === Promise::PENDING) { $promise->fulfill($lastYieldResult); } }; try { $advanceGenerator(); } catch (Exception $e) { $promise->reject($e); } return $promise; }