Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork19
Async utilities and fibers for ReactPHP.
License
reactphp/async
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Async utilities and fibers forReactPHP.
This library allows you to manage async control flow. It provides a number ofcombinators forPromise-based APIs.Instead of nesting or chaining promise callbacks, you can declare them as alist, which is resolved sequentially in an async manner.React/Async will not automagically change blocking code to be async. You needto have an actual event loop and non-blocking libraries interacting with thatevent loop for it to work. As long as you have a Promise-based API that runs inan event loop, it can be used with this library.
Table of Contents
This lightweight library consists only of a few simple functions.All functions reside under theReact\Async namespace.
The below examples refer to all functions with their fully-qualified names like this:
React\Async\await(…);As of PHP 5.6+ you can also import each required function into your code like this:
usefunctionReact\Async\await;await(…);
Alternatively, you can also use an import statement similar to this:
useReact\Async;Async\await(…);
Theasync(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>) function can be used toreturn an async function for a function that usesawait() internally.
This function is specifically designed to complement theawait() function.Theawait() function can be consideredblocking from theperspective of the calling code. You can avoid this blocking behavior bywrapping it in anasync() function call. Everything inside this functionwill still be blocked, but everything outside this function can be executedasynchronously without blocking:
Loop::addTimer(0.5,React\Async\async(function () {echo'a';React\Async\await(React\Promise\Timer\sleep(1.0));echo'c';}));Loop::addTimer(1.0,function () {echo'b';});// prints "a" at t=0.5s// prints "b" at t=1.0s// prints "c" at t=1.5s
See also theawait() function for more details.
Note that this function only works in tandem with theawait() function.In particular, this function does not "magically" make any blocking functionnon-blocking:
Loop::addTimer(0.5,React\Async\async(function () {echo'a';sleep(1);// broken: using PHP's blocking sleep() for demonstration purposesecho'c';}));Loop::addTimer(1.0,function () {echo'b';});// prints "a" at t=0.5s// prints "c" at t=1.5s: Correct timing, but wrong order// prints "b" at t=1.5s: Triggered too late because it was blocked
As an alternative, you should always make sure to use this function in tandemwith theawait() function and an async API returning a promiseas shown in the previous example.
Theasync() function is specifically designed for cases where it is usedas a callback (such as an event loop timer, event listener, or promisecallback). For this reason, it returns a new function wrapping the given$function instead of directly invoking it and returning its value.
usefunctionReact\Async\async;Loop::addTimer(1.0,async(function () { … }));$connection->on('close',async(function () { … }));$stream->on('data',async(function ($data) { … }));$promise->then(async(function (int$result) { … }));
You can invoke this wrapping function to invoke the given$function withany arguments given as-is. The function will always return a Promise whichwill be fulfilled with whatever your$function returns. Likewise, it willreturn a promise that will be rejected if you throw anException orThrowable from your$function. This allows you to easily createPromise-based functions:
$promise =React\Async\async(function ():int {$browser =newReact\Http\Browser();$urls = ['https://example.com/alice','https://example.com/bob' ];$bytes =0;foreach ($urlsas$url) {$response =React\Async\await($browser->get($url));assert($responseinstanceofPsr\Http\Message\ResponseInterface);$bytes +=$response->getBody()->getSize(); }return$bytes;})();$promise->then(function (int$bytes) {echo'Total size:' .$bytes .PHP_EOL;},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
The previous example usesawait() inside a loop to highlight howthis vastly simplifies consuming asynchronous operations. At the same time,this naive example does not leverage concurrent execution, as it willessentially "await" between each operation. In order to take advantage ofconcurrent execution within the given$function, you can "await" multiplepromises by using a singleawait() together with Promise-basedprimitives like this:
$promise =React\Async\async(function ():int {$browser =newReact\Http\Browser();$urls = ['https://example.com/alice','https://example.com/bob' ];$promises = [];foreach ($urlsas$url) {$promises[] =$browser->get($url); }try {$responses =React\Async\await(React\Promise\all($promises)); }catch (Exception$e) {foreach ($promisesas$promise) {$promise->cancel(); }throw$e; }$bytes =0;foreach ($responsesas$response) {assert($responseinstanceofPsr\Http\Message\ResponseInterface);$bytes +=$response->getBody()->getSize(); }return$bytes;})();$promise->then(function (int$bytes) {echo'Total size:' .$bytes .PHP_EOL;},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
The returned promise is implemented in such a way that it can be cancelledwhen it is still pending. Cancelling a pending promise will cancel any awaitedpromises inside that fiber or any nested fibers. As such, the following examplewill only outputab and cancel the pendingdelay().Theawait() calls in this example would throw aRuntimeExceptionfrom the cancelleddelay() call that bubbles up through the fibers.
$promise =async(staticfunction ():int {echo'a';await(async(staticfunction ():void {echo'b';delay(2);echo'c'; })());echo'd';returntime();})();$promise->cancel();await($promise);
Theawait(PromiseInterface<T> $promise): T function can be used toblock waiting for the given$promise to be fulfilled.
$result =React\Async\await($promise);
This function will only return after the given$promise has settled, i.e.either fulfilled or rejected. While the promise is pending, this functioncan be consideredblocking from the perspective of the calling code.You can avoid this blocking behavior by wrapping it in anasync() functioncall. Everything inside this function will still be blocked, but everythingoutside this function can be executed asynchronously without blocking:
Loop::addTimer(0.5,React\Async\async(function () {echo'a';React\Async\await(React\Promise\Timer\sleep(1.0));echo'c';}));Loop::addTimer(1.0,function () {echo'b';});// prints "a" at t=0.5s// prints "b" at t=1.0s// prints "c" at t=1.5s
See also theasync() function for more details.
Once the promise is fulfilled, this function will return whatever the promiseresolved to.
Once the promise is rejected, this will throw whatever the promise rejectedwith. If the promise did not reject with anException orThrowable, thenthis function will throw anUnexpectedValueException instead.
try {$result =React\Async\await($promise);// promise successfully fulfilled with $resultecho'Result:' .$result;}catch (Throwable$e) {// promise rejected with $eecho'Error:' .$e->getMessage();}
Thecoroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T> function can be used toexecute a Generator-based coroutine to "await" promises.
React\Async\coroutine(function () {$browser =newReact\Http\Browser();try {$response =yield$browser->get('https://example.com/');assert($responseinstanceofPsr\Http\Message\ResponseInterface);echo$response->getBody(); }catch (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL; }});
Using Generator-based coroutines is an alternative to directly using theunderlying promise APIs. For many use cases, this makes using promise-basedAPIs much simpler, as it resembles a synchronous code flow more closely.The above example performs the equivalent of directly using the promise APIs:
$browser =newReact\Http\Browser();$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface$response) {echo$response->getBody();},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Theyield keyword can be used to "await" a promise resolution. Internally,it will turn the entire given$function into aGenerator.This allows the execution to be interrupted and resumed at the same placewhen the promise is fulfilled. Theyield statement returns whatever thepromise is fulfilled with. If the promise is rejected, it will throw anException orThrowable.
Thecoroutine() function will always return a Promise which will befulfilled with whatever your$function returns. Likewise, it will returna promise that will be rejected if you throw anException orThrowablefrom your$function. This allows you to easily create Promise-basedfunctions:
$promise =React\Async\coroutine(function () {$browser =newReact\Http\Browser();$urls = ['https://example.com/alice','https://example.com/bob' ];$bytes =0;foreach ($urlsas$url) {$response =yield$browser->get($url);assert($responseinstanceofPsr\Http\Message\ResponseInterface);$bytes +=$response->getBody()->getSize(); }return$bytes;});$promise->then(function (int$bytes) {echo'Total size:' .$bytes .PHP_EOL;},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
The previous example uses ayield statement inside a loop to highlight howthis vastly simplifies consuming asynchronous operations. At the same time,this naive example does not leverage concurrent execution, as it willessentially "await" between each operation. In order to take advantage ofconcurrent execution within the given$function, you can "await" multiplepromises by using a singleyield together with Promise-based primitiveslike this:
$promise =React\Async\coroutine(function () {$browser =newReact\Http\Browser();$urls = ['https://example.com/alice','https://example.com/bob' ];$promises = [];foreach ($urlsas$url) {$promises[] =$browser->get($url); }try {$responses =yieldReact\Promise\all($promises); }catch (Exception$e) {foreach ($promisesas$promise) {$promise->cancel(); }throw$e; }$bytes =0;foreach ($responsesas$response) {assert($responseinstanceofPsr\Http\Message\ResponseInterface);$bytes +=$response->getBody()->getSize(); }return$bytes;});$promise->then(function (int$bytes) {echo'Total size:' .$bytes .PHP_EOL;},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Thedelay(float $seconds): void function can be used todelay program execution for duration given in$seconds.
React\Async\delay($seconds);
This function will only return after the given number of$seconds haveelapsed. If there are no other events attached to this loop, it will behavesimilar to PHP'ssleep() function.
echo'a';React\Async\delay(1.0);echo'b';// prints "a" at t=0.0s// prints "b" at t=1.0s
Unlike PHP'ssleep() function,this function may not necessarily halt execution of the entire process thread.Instead, it allows the event loop to run any other events attached to thesame loop until the delay returns:
echo'a';Loop::addTimer(1.0,function ():void {echo'b';});React\Async\delay(3.0);echo'c';// prints "a" at t=0.0s// prints "b" at t=1.0s// prints "c" at t=3.0s
This behavior is especially useful if you want to delay the program executionof a particular routine, such as when building a simple polling or retrymechanism:
try {something();}catch (Throwable) {// in case of error, retry after a short delayReact\Async\delay(1.0);something();}
Because this function only returns after some time has passed, it can beconsideredblocking from the perspective of the calling code. You can avoidthis blocking behavior by wrapping it in anasync() function call.Everything inside this function will still be blocked, but everything outsidethis function can be executed asynchronously without blocking:
Loop::addTimer(0.5,React\Async\async(function ():void {echo'a';React\Async\delay(1.0);echo'c';}));Loop::addTimer(1.0,function ():void {echo'b';});// prints "a" at t=0.5s// prints "b" at t=1.0s// prints "c" at t=1.5s
See also theasync() function for more details.
Internally, the$seconds argument will be used as a timer for the loop so thatit keeps running until this timer triggers. This implies that if you pass areally small (or negative) value, it will still start a timer and will thustrigger at the earliest possible time in the future.
The function is implemented in such a way that it can be cancelled when it isrunning inside anasync() function. Cancelling the resultingpromise will clean up any pending timers and throw aRuntimeException fromthe pending delay which in turn would reject the resulting promise.
$promise =async(function ():void {echo'a';delay(3.0);echo'b';})();Loop::addTimer(2.0,function ()use ($promise):void {$promise->cancel();});// prints "a" at t=0.0s// rejects $promise at t=2.0// never prints "b"
Theparallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>> function can be usedlike this:
<?phpuseReact\EventLoop\Loop;useReact\Promise\Promise;React\Async\parallel([function () {returnnewPromise(function ($resolve) { Loop::addTimer(1,function ()use ($resolve) {$resolve('Slept for a whole second'); }); }); },function () {returnnewPromise(function ($resolve) { Loop::addTimer(1,function ()use ($resolve) {$resolve('Slept for another whole second'); }); }); },function () {returnnewPromise(function ($resolve) { Loop::addTimer(1,function ()use ($resolve) {$resolve('Slept for yet another whole second'); }); }); },])->then(function (array$results) {foreach ($resultsas$result) {var_dump($result); }},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Theseries(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>> function can be usedlike this:
<?phpuseReact\EventLoop\Loop;useReact\Promise\Promise;React\Async\series([function () {returnnewPromise(function ($resolve) { Loop::addTimer(1,function ()use ($resolve) {$resolve('Slept for a whole second'); }); }); },function () {returnnewPromise(function ($resolve) { Loop::addTimer(1,function ()use ($resolve) {$resolve('Slept for another whole second'); }); }); },function () {returnnewPromise(function ($resolve) { Loop::addTimer(1,function ()use ($resolve) {$resolve('Slept for yet another whole second'); }); }); },])->then(function (array$results) {foreach ($resultsas$result) {var_dump($result); }},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Thewaterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T> function can be usedlike this:
<?phpuseReact\EventLoop\Loop;useReact\Promise\Promise;$addOne =function ($prev =0) {returnnewPromise(function ($resolve)use ($prev) { Loop::addTimer(1,function ()use ($prev,$resolve) {$resolve($prev +1); }); });};React\Async\waterfall([$addOne,$addOne,$addOne])->then(function ($prev) {echo"Final result is$prev\n";},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
- Implement queue()
The recommended way to install this library isthrough Composer.New to Composer?
This project followsSemVer.This will install the latest supported version from this branch:
composer require react/async:^4.3
See also theCHANGELOG for details about version upgrades.
This project aims to run on any platform and thus does not require any PHPextensions and supports running on PHP 8.1+.It'shighly recommended to use the latest supported PHP version for this project.
We're committed to providing long-term support (LTS) options and to provide asmooth upgrade path. If you're using an older PHP version, you may use the3.x branch (PHP 7.1+) or2.x branch (PHP 5.3+) which bothprovide a compatible API but do not take advantage of newer language features.You may target multiple versions at the same time to support a wider range ofPHP versions like this:
composer require"react/async:^4 || ^3 || ^2"To run the test suite, you first need to clone this repo and then install alldependenciesthrough Composer:
composer install
To run the test suite, go to the project root and run:
vendor/bin/phpunit
On top of this, we use PHPStan on max level to ensure type safety across the project:
vendor/bin/phpstan
MIT, seeLICENSE file.
This project is heavily influenced byasync.js.
About
Async utilities and fibers for ReactPHP.
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.