2016-12-29 21:09:49 +01:00
|
|
|
<?php
|
2016-08-16 06:46:26 +02:00
|
|
|
|
2016-05-24 05:48:28 +02:00
|
|
|
namespace Amp;
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2017-01-07 13:47:45 +01:00
|
|
|
use AsyncInterop\{ Loop, Promise };
|
2016-08-02 19:21:21 +02:00
|
|
|
|
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Wraps the callback in a promise/coroutine-aware function that automatically upgrades Generators to coroutines and
|
2016-12-29 23:57:08 +01:00
|
|
|
* calls rethrow() on the returned promises (or the coroutine created).
|
2016-08-02 19:21:21 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @param callable(...$args): \Generator|\AsyncInterop\Promise|mixed $callback
|
2016-08-02 19:21:21 +02:00
|
|
|
*
|
|
|
|
* @return callable(...$args): void
|
|
|
|
*/
|
2016-08-11 21:35:58 +02:00
|
|
|
function wrap(callable $callback): callable {
|
|
|
|
return function (...$args) use ($callback) {
|
|
|
|
$result = $callback(...$args);
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2016-08-02 19:21:21 +02:00
|
|
|
if ($result instanceof \Generator) {
|
2016-12-29 23:57:08 +01:00
|
|
|
$result = new Coroutine($result);
|
|
|
|
}
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2016-12-29 23:57:08 +01:00
|
|
|
if ($result instanceof Promise) {
|
|
|
|
rethrow($result);
|
2016-08-02 19:21:21 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Returns a new function that wraps $worker in a promise/coroutine-aware function that automatically upgrades
|
|
|
|
* Generators to coroutines. The returned function always returns a promise when invoked. If $worker throws, a failed
|
|
|
|
* promise is returned.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-08-12 21:50:44 +02:00
|
|
|
* @param callable(mixed ...$args): mixed $worker
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return callable(mixed ...$args): \AsyncInterop\Promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-08-11 21:35:58 +02:00
|
|
|
function coroutine(callable $worker): callable {
|
2016-11-14 20:59:21 +01:00
|
|
|
return function (...$args) use ($worker): Promise {
|
2016-08-12 21:50:44 +02:00
|
|
|
try {
|
|
|
|
$result = $worker(...$args);
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
return new Failure($exception);
|
|
|
|
}
|
2016-05-21 19:19:48 +02:00
|
|
|
|
2016-08-12 21:50:44 +02:00
|
|
|
if ($result instanceof \Generator) {
|
|
|
|
return new Coroutine($result);
|
|
|
|
}
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
if (!$result instanceof Promise) {
|
2016-08-12 21:50:44 +02:00
|
|
|
return new Success($result);
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-08-12 21:50:44 +02:00
|
|
|
return $result;
|
2016-05-21 19:19:48 +02:00
|
|
|
};
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Registers a callback that will forward the failure reason to the Loop error handler if the promise fails.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @param \AsyncInterop\Promise $promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function rethrow(Promise $promise) {
|
|
|
|
$promise->when(function ($exception) {
|
2016-05-21 16:44:52 +02:00
|
|
|
if ($exception) {
|
|
|
|
throw $exception;
|
|
|
|
}
|
2016-05-21 19:19:48 +02:00
|
|
|
});
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Runs the event loop until the promise is resolved. Should not be called within a running event loop.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @param \AsyncInterop\Promise $promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @return mixed Promise success value.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @throws \Throwable Promise failure reason.
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function wait(Promise $promise) {
|
2016-05-26 07:09:50 +02:00
|
|
|
$resolved = false;
|
2016-11-14 20:59:21 +01:00
|
|
|
Loop::execute(function () use (&$resolved, &$value, &$exception, $promise) {
|
|
|
|
$promise->when(function ($e, $v) use (&$resolved, &$value, &$exception) {
|
2016-05-21 19:19:48 +02:00
|
|
|
Loop::stop();
|
2016-05-26 07:09:50 +02:00
|
|
|
$resolved = true;
|
2016-05-21 19:19:48 +02:00
|
|
|
$exception = $e;
|
|
|
|
$value = $v;
|
|
|
|
});
|
2016-05-27 01:15:33 +02:00
|
|
|
}, Loop::get());
|
2016-05-26 07:09:50 +02:00
|
|
|
|
|
|
|
if (!$resolved) {
|
2016-12-11 16:12:42 +01:00
|
|
|
throw new \Error("Loop stopped without resolving promise");
|
2016-05-26 07:09:50 +02:00
|
|
|
}
|
2016-05-21 19:19:48 +02:00
|
|
|
|
|
|
|
if ($exception) {
|
|
|
|
throw $exception;
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
return $value;
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
|
|
|
* Pipe the promised value through the specified functor once it resolves.
|
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @param \AsyncInterop\Promise $promise
|
2016-05-21 19:19:48 +02:00
|
|
|
* @param callable(mixed $value): mixed $functor
|
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function pipe(Promise $promise, callable $functor): Promise {
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-05-21 19:19:48 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function ($exception, $value) use ($deferred, $functor) {
|
2016-05-21 19:19:48 +02:00
|
|
|
if ($exception) {
|
|
|
|
$deferred->fail($exception);
|
|
|
|
return;
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
try {
|
|
|
|
$deferred->resolve($functor($value));
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$deferred->fail($exception);
|
|
|
|
}
|
|
|
|
});
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2017-01-07 13:47:45 +01:00
|
|
|
* @param \AsyncInterop\Promise $promise
|
2016-06-15 04:40:04 +02:00
|
|
|
* @param string $className Exception class name to capture. Given callback will only be invoked if the failure reason
|
|
|
|
* is an instance of the given exception class name.
|
2016-08-11 21:35:58 +02:00
|
|
|
* @param callable(\Throwable $exception): mixed $functor
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function capture(Promise $promise, string $className, callable $functor): Promise {
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-05-21 19:19:48 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function ($exception, $value) use ($deferred, $className, $functor) {
|
2016-05-21 19:19:48 +02:00
|
|
|
if (!$exception) {
|
|
|
|
$deferred->resolve($value);
|
|
|
|
return;
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-06-15 04:40:04 +02:00
|
|
|
if (!$exception instanceof $className) {
|
|
|
|
$deferred->fail($exception);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
try {
|
|
|
|
$deferred->resolve($functor($exception));
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$deferred->fail($exception);
|
|
|
|
}
|
|
|
|
});
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Create an artificial timeout for any Promise.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* If the timeout expires before the promise is resolved, the returned promise fails with an instance of
|
2016-08-11 21:35:58 +02:00
|
|
|
* \Amp\TimeoutException.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @param \AsyncInterop\Promise $promise
|
2016-05-21 19:19:48 +02:00
|
|
|
* @param int $timeout Timeout in milliseconds.
|
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function timeout(Promise $promise, int $timeout): Promise {
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-07-12 18:20:06 +02:00
|
|
|
$resolved = false;
|
2016-05-21 19:19:48 +02:00
|
|
|
|
2016-07-12 18:20:06 +02:00
|
|
|
$watcher = Loop::delay($timeout, function () use (&$resolved, $deferred) {
|
|
|
|
if (!$resolved) {
|
|
|
|
$resolved = true;
|
|
|
|
$deferred->fail(new TimeoutException);
|
|
|
|
}
|
2016-05-23 07:44:35 +02:00
|
|
|
});
|
2016-12-17 15:28:19 +01:00
|
|
|
Loop::unreference($watcher);
|
2016-05-21 19:19:48 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function () use (&$resolved, $promise, $deferred, $watcher) {
|
2016-05-21 19:19:48 +02:00
|
|
|
Loop::cancel($watcher);
|
2016-07-12 18:20:06 +02:00
|
|
|
|
|
|
|
if ($resolved) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$resolved = true;
|
2016-11-14 20:59:21 +01:00
|
|
|
$deferred->resolve($promise);
|
2016-06-15 04:40:04 +02:00
|
|
|
});
|
2016-05-21 19:19:48 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Adapts any object with a then(callable $onFulfilled, callable $onRejected) method to a promise usable by
|
|
|
|
* components depending on placeholders implementing Promise.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
|
|
|
* @param object $thenable Object with a then() method.
|
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise Promise resolved by the $thenable object.
|
2016-05-23 17:19:37 +02:00
|
|
|
*
|
2016-08-11 21:35:58 +02:00
|
|
|
* @throws \Error If the provided object does not have a then() method.
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function adapt($thenable): Promise {
|
2016-05-22 20:24:39 +02:00
|
|
|
$deferred = new Deferred;
|
|
|
|
|
|
|
|
$thenable->then([$deferred, 'resolve'], [$deferred, 'fail']);
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Wraps the given callable $worker in a promise aware function that has the same number of arguments as $worker,
|
|
|
|
* but those arguments may be promises for the future argument value or just values. The returned function will
|
|
|
|
* return a promise for the return value of $worker and will never throw. The $worker function will not be called
|
|
|
|
* until each promise given as an argument is fulfilled. If any promise provided as an argument fails, the
|
|
|
|
* promise returned by the returned function will be failed for the same reason. The promise succeeds with
|
2016-05-21 19:19:48 +02:00
|
|
|
* the return value of $worker or failed if $worker throws.
|
|
|
|
*
|
|
|
|
* @param callable $worker
|
|
|
|
*
|
|
|
|
* @return callable
|
|
|
|
*/
|
2016-08-11 21:35:58 +02:00
|
|
|
function lift(callable $worker): callable {
|
2016-05-21 16:44:52 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* @param mixed ...$args Promises or values.
|
2016-05-21 16:44:52 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-21 16:44:52 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
return function (...$args) use ($worker): Promise {
|
2016-05-22 17:53:13 +02:00
|
|
|
foreach ($args as $key => $arg) {
|
2016-11-14 20:59:21 +01:00
|
|
|
if (!$arg instanceof Promise) {
|
2016-05-22 17:53:13 +02:00
|
|
|
$args[$key] = new Success($arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
if (1 === \count($args)) {
|
2016-05-22 17:53:13 +02:00
|
|
|
return pipe($args[0], $worker);
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
return pipe(all($args), function (array $args) use ($worker) {
|
2017-02-10 05:34:24 +01:00
|
|
|
\ksort($args); // Needed to ensure correct argument order.
|
2016-08-11 21:35:58 +02:00
|
|
|
return $worker(...$args);
|
2016-05-21 16:44:52 +02:00
|
|
|
});
|
2016-05-21 19:19:48 +02:00
|
|
|
};
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Returns a promise that is resolved when all promises are resolved. The returned promise will not fail.
|
|
|
|
* Returned promise succeeds with a two-item array delineating successful and failed promise results,
|
2016-07-19 21:34:17 +02:00
|
|
|
* with keys identical and corresponding to the original given array.
|
|
|
|
*
|
|
|
|
* This function is the same as some() with the notable exception that it will never fail even
|
2016-11-14 20:59:21 +01:00
|
|
|
* if all promises in the array resolve unsuccessfully.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @param Promise[] $promises
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-23 17:19:37 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @throws \Error If a non-Promise is in the array.
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function any(array $promises): Promise {
|
|
|
|
if (empty($promises)) {
|
2016-07-19 21:34:17 +02:00
|
|
|
return new Success([[], []]);
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$pending = \count($promises);
|
2016-07-19 21:34:17 +02:00
|
|
|
$errors = [];
|
|
|
|
$values = [];
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
foreach ($promises as $key => $promise) {
|
|
|
|
if (!$promise instanceof Promise) {
|
|
|
|
throw new \Error("Non-promise provided");
|
2016-05-22 17:53:13 +02:00
|
|
|
}
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function ($error, $value) use (&$pending, &$errors, &$values, $key, $deferred) {
|
2016-07-19 21:34:17 +02:00
|
|
|
if ($error) {
|
|
|
|
$errors[$key] = $error;
|
|
|
|
} else {
|
|
|
|
$values[$key] = $value;
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-07-19 21:34:17 +02:00
|
|
|
if (--$pending === 0) {
|
|
|
|
$deferred->resolve([$errors, $values]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Returns a promise that succeeds when all promises succeed, and fails if any promise fails. Returned
|
|
|
|
* promise succeeds with an array of values used to succeed each contained promise, with keys corresponding to
|
|
|
|
* the array of promises.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @param Promise[] $promises
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-23 17:19:37 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @throws \Error If a non-Promise is in the array.
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function all(array $promises): Promise {
|
|
|
|
if (empty($promises)) {
|
2016-05-22 17:53:13 +02:00
|
|
|
return new Success([]);
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$pending = \count($promises);
|
2016-05-24 04:32:41 +02:00
|
|
|
$resolved = false;
|
2016-05-21 19:19:48 +02:00
|
|
|
$values = [];
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
foreach ($promises as $key => $promise) {
|
|
|
|
if (!$promise instanceof Promise) {
|
|
|
|
throw new \Error("Non-promise provided");
|
2016-05-22 17:53:13 +02:00
|
|
|
}
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function ($exception, $value) use (&$values, &$pending, &$resolved, $key, $deferred) {
|
2016-05-24 04:32:41 +02:00
|
|
|
if ($resolved) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
if ($exception) {
|
2016-05-24 04:32:41 +02:00
|
|
|
$resolved = true;
|
2016-05-21 19:19:48 +02:00
|
|
|
$deferred->fail($exception);
|
|
|
|
return;
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
$values[$key] = $value;
|
2016-05-21 16:44:52 +02:00
|
|
|
if (0 === --$pending) {
|
2016-05-21 19:19:48 +02:00
|
|
|
$deferred->resolve($values);
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
2016-06-15 04:40:04 +02:00
|
|
|
});
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Returns a promise that succeeds when the first promise succeeds, and fails only if all promises fail.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @param Promise[] $promises
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-23 17:19:37 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* @throws \Error If the array is empty or a non-Promise is in the array.
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function first(array $promises): Promise {
|
|
|
|
if (empty($promises)) {
|
|
|
|
throw new \Error("No promises provided");
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$pending = \count($promises);
|
2016-05-24 04:32:41 +02:00
|
|
|
$resolved = false;
|
2016-05-21 19:19:48 +02:00
|
|
|
$exceptions = [];
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
foreach ($promises as $key => $promise) {
|
|
|
|
if (!$promise instanceof Promise) {
|
|
|
|
throw new \Error("Non-promise provided");
|
2016-05-22 17:53:13 +02:00
|
|
|
}
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function ($exception, $value) use (&$exceptions, &$pending, &$resolved, $key, $deferred) {
|
2016-05-24 04:32:41 +02:00
|
|
|
if ($resolved) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
if (!$exception) {
|
2016-05-24 04:32:41 +02:00
|
|
|
$resolved = true;
|
2016-05-21 19:19:48 +02:00
|
|
|
$deferred->resolve($value);
|
|
|
|
return;
|
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
$exceptions[$key] = $exception;
|
|
|
|
if (0 === --$pending) {
|
2016-05-24 17:39:19 +02:00
|
|
|
$deferred->fail(new MultiReasonException($exceptions));
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-06-15 04:40:04 +02:00
|
|
|
});
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Resolves with a two-item array delineating successful and failed Promise results.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2016-11-14 20:59:21 +01:00
|
|
|
* The returned promise will only fail if ALL of the promises fail.
|
2016-07-31 07:31:04 +02:00
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
* @param Promise[] $promises
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function some(array $promises): Promise {
|
|
|
|
if (empty($promises)) {
|
|
|
|
throw new \Error("No promises provided");
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$pending = \count($promises);
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-22 17:53:13 +02:00
|
|
|
$deferred = new Deferred;
|
2016-05-21 19:19:48 +02:00
|
|
|
$values = [];
|
|
|
|
$exceptions = [];
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
foreach ($promises as $key => $promise) {
|
|
|
|
if (!$promise instanceof Promise) {
|
|
|
|
throw new \Error("Non-promise provided");
|
2016-05-22 17:53:13 +02:00
|
|
|
}
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise->when(function ($exception, $value) use (&$values, &$exceptions, &$pending, $key, $deferred) {
|
2016-05-21 19:19:48 +02:00
|
|
|
if ($exception) {
|
2016-05-21 16:44:52 +02:00
|
|
|
$exceptions[$key] = $exception;
|
2016-07-31 07:31:04 +02:00
|
|
|
} else {
|
|
|
|
$values[$key] = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 === --$pending) {
|
|
|
|
if (empty($values)) {
|
2016-05-24 17:39:19 +02:00
|
|
|
$deferred->fail(new MultiReasonException($exceptions));
|
2016-07-31 07:31:04 +02:00
|
|
|
return;
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-07-31 07:31:04 +02:00
|
|
|
$deferred->resolve([$exceptions, $values]);
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-06-15 04:40:04 +02:00
|
|
|
});
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
|
|
|
|
2016-11-14 20:59:21 +01:00
|
|
|
return $deferred->promise();
|
2016-05-21 19:19:48 +02:00
|
|
|
}
|
2016-05-21 16:44:52 +02:00
|
|
|
|
2016-05-21 19:19:48 +02:00
|
|
|
/**
|
2016-11-14 20:59:21 +01:00
|
|
|
* Maps the callback to each promise as it succeeds. Returns an array of promises resolved by the return
|
|
|
|
* callback value of the callback function. The callback may return promises or throw exceptions to fail
|
|
|
|
* promises in the array. If a promise in the passed array fails, the callback will not be called and the
|
|
|
|
* promise in the array fails for the same reason. Tip: Use all() or any() to determine when all
|
|
|
|
* promises in the array have been resolved.
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
|
|
|
* @param callable(mixed $value): mixed $callback
|
2016-11-14 20:59:21 +01:00
|
|
|
* @param Promise[] ...$promises
|
2016-05-21 19:19:48 +02:00
|
|
|
*
|
2017-01-07 13:47:45 +01:00
|
|
|
* @return \AsyncInterop\Promise[] Array of promises resolved with the result of the mapped function.
|
2016-05-21 19:19:48 +02:00
|
|
|
*/
|
2016-11-14 20:59:21 +01:00
|
|
|
function map(callable $callback, array ...$promises): array {
|
|
|
|
foreach ($promises as $promiseSet) {
|
|
|
|
foreach ($promiseSet as $promise) {
|
|
|
|
if (!$promise instanceof Promise) {
|
|
|
|
throw new \Error("Non-promise provided");
|
2016-05-23 17:19:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-10 05:17:45 +01:00
|
|
|
return \array_map(lift($callback), ...$promises);
|
2016-05-21 16:44:52 +02:00
|
|
|
}
|
2016-07-19 06:29:19 +02:00
|
|
|
|
2016-12-30 19:50:09 +01:00
|
|
|
/**
|
2017-01-04 02:10:27 +01:00
|
|
|
* Creates a stream from the given iterable, emitting the each value. The iterable may contain promises. If any promise
|
|
|
|
* fails, the stream will fail with the same reason.
|
|
|
|
*
|
2016-12-30 19:50:09 +01:00
|
|
|
* @param array|\Traversable $iterable
|
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @return \Amp\Stream
|
2016-12-30 19:50:09 +01:00
|
|
|
*
|
|
|
|
* @throws \TypeError If the argument is not an array or instance of \Traversable.
|
|
|
|
*/
|
2017-01-04 02:10:27 +01:00
|
|
|
function stream(/* iterable */ $iterable): Stream {
|
2016-12-30 19:50:09 +01:00
|
|
|
if (!$iterable instanceof \Traversable && !\is_array($iterable)) {
|
|
|
|
throw new \TypeError("Must provide an array or instance of Traversable");
|
|
|
|
}
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
return new Producer(function (callable $emit) use ($iterable) {
|
2016-12-30 19:50:09 +01:00
|
|
|
foreach ($iterable as $value) {
|
|
|
|
yield $emit($value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-19 06:23:25 +02:00
|
|
|
/**
|
2017-01-04 02:10:27 +01:00
|
|
|
* @param \Amp\Stream $stream
|
2016-07-19 06:23:25 +02:00
|
|
|
* @param callable(mixed $value): mixed $onNext
|
|
|
|
* @param callable(mixed $value): mixed|null $onComplete
|
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @return \Amp\Stream
|
2016-07-19 06:23:25 +02:00
|
|
|
*/
|
2017-01-04 02:10:27 +01:00
|
|
|
function each(Stream $stream, callable $onNext, callable $onComplete = null): Stream {
|
2017-02-10 05:17:45 +01:00
|
|
|
$listener = new Listener($stream);
|
|
|
|
return new Producer(function (callable $emit) use ($listener, $onNext, $onComplete) {
|
|
|
|
while (yield $listener->advance()) {
|
|
|
|
yield $emit($onNext($listener->getCurrent()));
|
2016-08-23 20:22:24 +02:00
|
|
|
}
|
2016-07-19 06:23:25 +02:00
|
|
|
if ($onComplete === null) {
|
2017-02-10 05:17:45 +01:00
|
|
|
return $listener->getResult();
|
2016-07-19 06:23:25 +02:00
|
|
|
}
|
2017-02-10 05:17:45 +01:00
|
|
|
return $onComplete($listener->getResult());
|
2016-07-19 06:23:25 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-04 02:10:27 +01:00
|
|
|
* @param \Amp\Stream $stream
|
2016-07-19 06:23:25 +02:00
|
|
|
* @param callable(mixed $value): bool $filter
|
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @return \Amp\Stream
|
2016-07-19 06:23:25 +02:00
|
|
|
*/
|
2017-01-04 02:10:27 +01:00
|
|
|
function filter(Stream $stream, callable $filter): Stream {
|
2017-02-10 05:17:45 +01:00
|
|
|
$listener = new Listener($stream);
|
|
|
|
return new Producer(function (callable $emit) use ($listener, $filter) {
|
|
|
|
while (yield $listener->advance()) {
|
|
|
|
if ($filter($listener->getCurrent())) {
|
|
|
|
yield $emit($listener->getCurrent());
|
2016-07-19 06:23:25 +02:00
|
|
|
}
|
2016-08-23 20:22:24 +02:00
|
|
|
}
|
2017-02-10 05:17:45 +01:00
|
|
|
return $listener->getResult();
|
2016-07-19 06:23:25 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-05-24 18:47:14 +02:00
|
|
|
/**
|
2017-01-04 02:10:27 +01:00
|
|
|
* Creates a stream that emits values emitted from any stream in the array of streams.
|
2016-05-24 18:47:14 +02:00
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @param \Amp\Stream[] $streams
|
2016-05-24 18:47:14 +02:00
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @return \Amp\Stream
|
2016-05-24 18:47:14 +02:00
|
|
|
*/
|
2017-01-04 02:10:27 +01:00
|
|
|
function merge(array $streams): Stream {
|
|
|
|
$emitter = new Emitter;
|
2016-12-16 00:28:22 +01:00
|
|
|
$pending = true;
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
foreach ($streams as $stream) {
|
|
|
|
if (!$stream instanceof Stream) {
|
|
|
|
throw new \Error("Non-stream provided");
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|
2017-01-04 02:10:27 +01:00
|
|
|
$stream->listen(function ($value) use (&$pending, $emitter) {
|
2016-12-16 00:28:22 +01:00
|
|
|
if ($pending) {
|
2017-01-04 02:10:27 +01:00
|
|
|
return $emitter->emit($value);
|
2016-12-16 00:28:22 +01:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
});
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
all($streams)->when(function ($exception, array $values = null) use (&$pending, $emitter) {
|
2016-12-16 00:28:22 +01:00
|
|
|
$pending = false;
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2016-08-23 20:22:24 +02:00
|
|
|
if ($exception) {
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter->fail($exception);
|
2016-08-23 20:22:24 +02:00
|
|
|
return;
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter->resolve($values);
|
2016-05-24 18:47:14 +02:00
|
|
|
});
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
return $emitter->stream();
|
2016-07-19 06:23:25 +02:00
|
|
|
}
|
|
|
|
|
2016-08-01 18:10:59 +02:00
|
|
|
/**
|
2017-01-04 02:10:27 +01:00
|
|
|
* Concatenates the given streams into a single stream, emitting values from a single stream at a time. The
|
|
|
|
* prior stream must complete before values are emitted from any subsequent stream. Streams are concatenated
|
2016-08-01 18:10:59 +02:00
|
|
|
* in the order given (iteration order of the array).
|
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @param array $streams
|
2016-08-01 18:10:59 +02:00
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @return \Amp\Stream
|
2016-08-01 18:10:59 +02:00
|
|
|
*/
|
2017-01-04 02:10:27 +01:00
|
|
|
function concat(array $streams): Stream {
|
|
|
|
foreach ($streams as $stream) {
|
|
|
|
if (!$stream instanceof Stream) {
|
|
|
|
throw new \Error("Non-stream provided");
|
2016-08-01 18:10:59 +02:00
|
|
|
}
|
|
|
|
}
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter = new Emitter;
|
2016-08-23 20:22:24 +02:00
|
|
|
$subscriptions = [];
|
|
|
|
$previous = [];
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise = all($previous);
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
foreach ($streams as $stream) {
|
|
|
|
$generator = function ($value) use ($emitter, $promise) {
|
2016-12-16 00:28:22 +01:00
|
|
|
static $pending = true, $failed = false;
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2016-12-16 00:28:22 +01:00
|
|
|
if ($failed) {
|
|
|
|
return;
|
|
|
|
}
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2016-12-16 00:28:22 +01:00
|
|
|
if ($pending) {
|
|
|
|
try {
|
|
|
|
yield $promise;
|
|
|
|
$pending = false;
|
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$failed = true;
|
2017-01-04 02:10:27 +01:00
|
|
|
return; // Prior stream failed.
|
2016-12-16 00:28:22 +01:00
|
|
|
}
|
2016-08-01 18:10:59 +02:00
|
|
|
}
|
2017-01-07 13:47:45 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
yield $emitter->emit($value);
|
2016-12-30 19:50:09 +01:00
|
|
|
};
|
2017-01-04 02:10:27 +01:00
|
|
|
$subscriptions[] = $stream->listen(function ($value) use ($generator) {
|
2016-12-30 19:50:09 +01:00
|
|
|
return new Coroutine($generator($value));
|
|
|
|
});
|
2017-01-04 02:10:27 +01:00
|
|
|
$previous[] = $stream;
|
2016-11-14 20:59:21 +01:00
|
|
|
$promise = all($previous);
|
2016-08-23 20:22:24 +02:00
|
|
|
}
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
$promise->when(function ($exception, array $values = null) use ($emitter) {
|
2016-08-23 20:22:24 +02:00
|
|
|
if ($exception) {
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter->fail($exception);
|
2016-08-23 20:22:24 +02:00
|
|
|
return;
|
2016-08-01 18:10:59 +02:00
|
|
|
}
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter->resolve($values);
|
2016-08-01 18:10:59 +02:00
|
|
|
});
|
2016-12-11 16:17:51 +01:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
return $emitter->stream();
|
2016-08-01 18:10:59 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 18:47:14 +02:00
|
|
|
/**
|
2017-01-04 02:10:27 +01:00
|
|
|
* Returns a stream that emits a value every $interval milliseconds after (up to $count times). The value emitted
|
|
|
|
* is an integer of the number of times the stream emitted a value.
|
2016-05-24 18:47:14 +02:00
|
|
|
*
|
|
|
|
* @param int $interval Time interval between emitted values in milliseconds.
|
2016-07-20 15:53:18 +02:00
|
|
|
* @param int $count Number of values to emit. PHP_INT_MAX by default.
|
2016-05-24 18:47:14 +02:00
|
|
|
*
|
2017-01-04 02:10:27 +01:00
|
|
|
* @return \Amp\Stream
|
2016-08-12 23:58:53 +02:00
|
|
|
*
|
|
|
|
* @throws \Error If the number of times to emit is not a positive value.
|
2016-05-24 18:47:14 +02:00
|
|
|
*/
|
2017-01-04 02:10:27 +01:00
|
|
|
function interval(int $interval, int $count = PHP_INT_MAX): Stream {
|
2016-07-20 15:53:18 +02:00
|
|
|
if (0 >= $count) {
|
2016-08-12 23:58:53 +02:00
|
|
|
throw new \Error("The number of times to emit must be a positive value");
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter = new Emitter;
|
2016-05-27 01:20:05 +02:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
Loop::repeat($interval, function ($watcher) use (&$i, $emitter, $count) {
|
|
|
|
$emitter->emit(++$i);
|
2016-05-27 01:20:05 +02:00
|
|
|
|
|
|
|
if ($i === $count) {
|
2016-05-24 18:47:14 +02:00
|
|
|
Loop::cancel($watcher);
|
2017-01-04 02:10:27 +01:00
|
|
|
$emitter->resolve();
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|
|
|
|
});
|
2016-05-27 01:20:05 +02:00
|
|
|
|
2017-01-04 02:10:27 +01:00
|
|
|
return $emitter->stream();
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|