A library to ease handling promises in ReactPHP.
- Install
- Usage
- License
- Functions
- async Run blocking code in async mode
- execute Execute a command
- is_done Instantly return if the Promise is resolved or rejected
- resolve Use yield with promises!
- retry Retry a function multiple times
- silent Silently resolve
- sleep Non-blocking sleep
- timeout Adds a timeout to a Promise
- timer Allows to time a Promise
- wait Make async code synchronous
- wait_memory Waits for a number of RAM bytes to be available
- PHP file functions PHP's blocking file functions in async mode
composer require choval/async- PHP 8.0+
- for
async&execute:- ext-pcntl
- ext-posix
- ext-sockets
Having the following function
function future($i=0)
{
return new React\Promise\FulfilledPromise($i+1);
}The ugly way:
future()
->then(function ($i) {
return future($i);
})
->then(function ($i) {
return future($i);
})
->then(function ($i) {
return future($i);
})
->then(function ($i) {
return future($i);
})
->then(function ($i) {
echo $i;
});
// Prints 5, but that chain nightmare...Using yield, remember future() is returning a Promise.
And we're not blocking other events in the loop ;-)
Async\resolve(function () {
$i = yield future();
$i = yield future($i);
$i = yield future($i);
$i = yield future($i);
$i = yield future($i);
echo $i;
});
// Prints 5 as well ;-)Or in a while-loop
Async\resolve(function () {
$i = 0;
while($i<5) {
$i = yield future($i);
}
echo $i;
});Checks if a Promise has been resolved or rejected. This returns a boolean, not a Promise.
$defer = new React\Promise\Deferred();
$loop->addTimer(1, function () use ($defer) {
$defer->resolve(true);
});
$promise = $defer->promise();
$i = 0;
function future($i=0)
{
return new React\Promise\FulfilledPromise($i+1);
}
while(!Async\is_done($promise)) {
$i++;
}
echo "Promise finished with $i loops\n";This is what will let you yield promises, it's like Node.js await.
$promise = Async\resolve(function () {
yield 1;
yield 2;
return 'Wazza';
});
// $promise resolves with WazzaTake for example the following async events.
$defer1 = new React\Promise\Deferred();
$loop->addTimer(1, function () use ($defer1) {
$defer1->resolve('hello');
});
$defer2 = new React\Promise\Deferred();
$loop->addTimer(0.5, function () use ($defer2) {
$defer2->resolve('world');
});
$promise = Async\resolve(function () use ($defer1, $defer2) {
$out = [];
$out[] = yield $defer1->promise();
$out[] = yield $defer2->promise();
return implode(' ', $out);
});$promise resolves with hello world in 1 sec, despite the second promise resolving first.
What if you need to run multiple async simultaneously?
$promise = Async\resolve(function () {
$fetch = [
'bing' =>
Async\execute('curl https://bing.com/'),
'duckduckgo' =>
Async\execute('curl https://duckduckgo.com/'),
'google' =>
Async\execute('curl https://google.com/'),
];
$sources = yield React\Promise\all($fetch);
return $sources;
});Similar to resolve, but will catch any Exception and save it in the second parameter.
If it fails, the promise will resolve with null.
$fn = function () {
throw new \Exception('hey!');
};
$promise = Async\silent($fn, $e);
// Promise resolves with null
// $e will hold an the hey! exceptionExecutes a command asynchronously.
Returns a Promise with the output of the command.
Async\execute('echo "Wazza"')
->then(function ($output) {
// $output contains Wazza\n
})
->otherwise(function ($e) {
// Throws an Exception if the execution fails
// ie: 127 if the command does not exist
$exitCode = $e->getCode();
});A timeout parameter (in seconds) can be passed.
An asynchronous sleep function. This won't block other events.
$promise = Async\resolve(function () {
$start = time();
yield Async\sleep(2);
$end = time();
return $end-$start;
});
// $promise resolves in ~2 secondsRemember this is a non-blocking sleep, if you do not wait for it or yield inside an Async\resolve, the Promise will solve in the background.
$start = time();
Async\sleep(2);
$end = time();
// $start and $end will be the sameAlso knowsn as sync, makes asynchronous code blocking. Use this when you need to use an async library in a sync/blocking scenario.
This function receives one of the following: Generator, Closure or PromiseInterface.
$start = time();
Async\wait(Async\sleep(2));
$end = time();
// $end == $start+2;A second float parameter is a timeout in seconds, defaults to no timeout.
A third float parameter is the interval at which to check, defaults to 0.01 secs. A low interval will consume much more CPU.
Have a piece of blocking code that you need to run in async?
Use this, just keep in mind it is using pcntl_fork.
First parameter is a callable, second parameter is an array of parameters for the callable.
$blocking_code = function ($secs) {
sleep($secs);
return time();
}
$secs = 1;
$promises = [];
$promises[] = Async\async($blocking_code, [$secs]);
$promises[] = Async\async($blocking_code, [$secs]);
$promises[] = Async\async($blocking_code, [$secs]);
$promises[] = Async\async($blocking_code, [$secs]);
$base = time()+$secs;
$times = Async\wait(React\Promise\all($promises));
foreach ($times as $time) {
// $time === $base
}There's a limit of 50 simultaneously running async forks.
This limit can be changed by calling Async\set_forks_limit.
This limit is counted for Async\execute as well.
Async\set_forks_limit(100);
echo Async\get_forks_limit(); // 100When the limit is reached, the code will wait for any previous fork to finish before continuing, keeping a max of async forks at the set forks limit.
Runs a function (Closure/Generator) up to retries times for a "good" return. Otherwise, returns the last Exception.
This function can also ignore a set of Exception classes or messages.
$times = 5;
$func = function () use (&$times) {
if(--$times) {
throw new \Exception('bad error');
}
return 'ok';
};
$retries = 6;
Async\retry($func, $retries, 0.1, 'bad error')
->then(function ($res) {
// $res is 'ok'
});/**
* @param callable $func
* @param int $retries=10 (optional)
* @param float $frequency=0.001 (optional)
* @param string $ignoress (optional) The Throwable class to catch or string to match against Exception->getMessage()
*
* @return Promise
*/Similar to React\Promise\Timer\timeout(), but allows a Generator or Closure too.
$func = function () {
yield Async\sleep(2);
return true;
};
Async\wait(Async\timeout($func, 1.5));
// Throws an Exception due to the timeout 1.5 < 2Saves the number of elapsed microseconds (float).
Async\wait(function () {
Async\timer(function () {
Async\sleep(0.1);
}, $msecs);
print_r($msecs); // ~100ms
});Waits for a number of memory bytes to be available.
This is used inside loops to avoid memory exhaution due to multiple Promises being created and left in background.
Async\wait(function () {
$loop = 20000;
$mem = 1024*1024*16; // 16MB
while($loop--) {
yield Async\waitMemory($mem);
Async\sleep(1);
}
});A second parameter can be passed for the frequency to run the check.
Returns the number of bytes remaining (memory_limit - memory_get_usage()).
A recursive glob with an ignore parameter.
Consider the following files:
/files/
/files/a.txt
/files/b.txt
/files/a.php
/files/b.php
/files/c.php
/files/1/a.txt
/files/1/a.php
/files/1/b.php
/files/1/c.php
/files/2/a.php
/files/2/b.php
/files/2/c.php
$files = Async\wait(Async\rglob('/files/*.php', 'a'));
/*
$files has:
/files/b.php
/files/c.php
/files/1/b.php
/files/1/c.php
/files/2/b.php
/files/2/c.php
*/The following functions are available with the same parameters as their PHP versions, but run using Async\async and take an optional LoopInterface as their first parameter.
These are not production tested/optimized. Please use with caution.
file_get_contents
file_put_contents
file_exists
is_file
is_dir
is_link
sha1_file
md5_file
mime_content_type
realpath
fileatime
filectime
filemtime
file
filesize
copy
rename
unlink
touch
mkdir
rmdir
scandir
glob
Example:
$lines = Async\wait(Async\file('/etc/hosts'));
var_dump($lines);MIT, see LICENSE.