Skip to content

reactphp/async

 
 

Repository files navigation

Async

CI status installs on Packagist

Async utilities for ReactPHP.

This library allows you to manage async control flow. It provides a number of combinators for Promise-based APIs. Instead of nesting or chaining promise callbacks, you can declare them as a list, which is resolved sequentially in an async manner. React/Async will not automagically change blocking code to be async. You need to have an actual event loop and non-blocking libraries interacting with that event loop for it to work. As long as you have a Promise-based API that runs in an event loop, it can be used with this library.

Table of Contents

Usage

This lightweight library consists only of a few simple functions. All functions reside under the React\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:

use function React\Async\await;

await(…);

Alternatively, you can also use an import statement similar to this:

use React\Async;

Async\await(…);

await()

The await(PromiseInterface $promise): mixed function can be used to block 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 function will assume control over the event loop. Internally, it will run() the default loop until the promise settles and then calls stop() to terminate execution of the loop. This means this function is more suited for short-lived promise executions when using promise-based APIs is not feasible. For long-running applications, using promise-based APIs by leveraging chained then() calls is usually preferable.

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

Once the promise is rejected, this will throw whatever the promise rejected with. If the promise did not reject with an Exception or Throwable (PHP 7+), then this function will throw an UnexpectedValueException instead.

try {
    $result = React\Async\await($promise);
    // promise successfully fulfilled with $result
    echo 'Result: ' . $result;
} catch (Throwable $e) {
    // promise rejected with $e
    echo 'Error: ' . $e->getMessage();
}

delay()

The delay(float $seconds): void function can be used to delay program execution for duration given in $seconds.

React\Async\delay($seconds);

This function will only return after the given number of $seconds have elapsed. If there are no other events attached to this loop, it will behave similar to PHP's sleep() function.

echo 'a';
React\Async\delay(1.0);
echo 'b';

// prints "a" at t=0.0s
// prints "b" at t=1.0s

Unlike PHP's sleep() 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 the same loop until the delay returns:

echo 'a';
Loop::addTimer(1.0, function () {
    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 execution of a particular routine, such as when building a simple polling or retry mechanism:

try {
    something();
} catch (Exception $e) {
    // in case of error, retry after a short delay
    React\Async\delay(1.0);
    something();
}

Because this function only returns after some time has passed, it can be considered blocking from the perspective of the calling code. While the delay is running, this function will assume control over the event loop. Internally, it will run() the default loop until the delay returns and then calls stop() to terminate execution of the loop. This means this function is more suited for short-lived promise executions when using promise-based APIs is not feasible. For long-running applications, using promise-based APIs by leveraging chained then() calls is usually preferable.

Internally, the $seconds argument will be used as a timer for the loop so that it keeps running until this timer triggers. This implies that if you pass a really small (or negative) value, it will still start a timer and will thus trigger at the earliest possible time in the future.

parallel()

The parallel(array<callable():PromiseInterface<mixed>> $tasks): PromiseInterface<array<mixed>> function can be used like this:

<?php

use React\EventLoop\Loop;
use React\Promise\Promise;

React\Async\parallel([
    function () {
        return new Promise(function ($resolve) {
            Loop::addTimer(1, function () use ($resolve) {
                $resolve('Slept for a whole second');
            });
        });
    },
    function () {
        return new Promise(function ($resolve) {
            Loop::addTimer(1, function () use ($resolve) {
                $resolve('Slept for another whole second');
            });
        });
    },
    function () {
        return new Promise(function ($resolve) {
            Loop::addTimer(1, function () use ($resolve) {
                $resolve('Slept for yet another whole second');
            });
        });
    },
])->then(function (array $results) {
    foreach ($results as $result) {
        var_dump($result);
    }
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

series()

The series(array<callable():PromiseInterface<mixed>> $tasks): PromiseInterface<array<mixed>> function can be used like this:

<?php

use React\EventLoop\Loop;
use React\Promise\Promise;

React\Async\series([
    function () {
        return new Promise(function ($resolve) {
            Loop::addTimer(1, function () use ($resolve) {
                $resolve('Slept for a whole second');
            });
        });
    },
    function () {
        return new Promise(function ($resolve) {
            Loop::addTimer(1, function () use ($resolve) {
                $resolve('Slept for another whole second');
            });
        });
    },
    function () {
        return new Promise(function ($resolve) {
            Loop::addTimer(1, function () use ($resolve) {
                $resolve('Slept for yet another whole second');
            });
        });
    },
])->then(function (array $results) {
    foreach ($results as $result) {
        var_dump($result);
    }
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

waterfall()

The waterfall(array<callable(mixed=):PromiseInterface<mixed>> $tasks): PromiseInterface<mixed> function can be used like this:

<?php

use React\EventLoop\Loop;
use React\Promise\Promise;

$addOne = function ($prev = 0) {
    return new Promise(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;
});

Todo

  • Implement queue()

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version from this branch:

composer require react/async:^2.2

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on legacy PHP 5.3 through current PHP 8+. It's highly recommended to use the latest supported PHP version for this project.

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

composer install

To run the test suite, go to the project root and run:

vendor/bin/phpunit

License

MIT, see LICENSE file.

This project is heavily influenced by async.js.

About

Async utilities and fibers for ReactPHP.

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  
  •  
  •  

Contributors 6

Languages