choval/async

此包已被废弃,不再维护。没有建议的替代包。

一组帮助在 async 中编码的函数

v0.7.1 2022-05-30 20:32 UTC

README

tests

Choval\Async

一个库,用于简化在 ReactPHP 中处理承诺

安装

composer require choval/async

要求

  • PHP 8.0+
  • 对于 async & execute
    • ext-pcntl
    • ext-posix
    • ext-sockets

用法

以下是一个函数的示例

function future($i=0)
{
    return new React\Promise\FulfilledPromise($i+1);
}

丑陋的方式

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...

使用 yield,记住 future() 是返回一个 Promise
并且在循环中不会阻塞其他事件 ;-)

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 ;-)

或者在while循环中

Async\resolve(function () {
    $i = 0;
    while($i<5) {
        $i = yield future($i);
    }
    echo $i;
});

函数

is_done

检查一个 Promise 是否已被 解决拒绝。这返回一个布尔值,而不是一个 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";

resolve

这就是让您 yield 承诺的方式,它类似于 Node.js await

$promise = Async\resolve(function () {
    yield 1;
    yield 2;
    return 'Wazza';
});
// $promise resolves with Wazza

例如以下异步事件。

$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 在1秒后解决为 hello world,尽管第二个承诺首先解决。

如果您需要同时运行多个异步操作怎么办?

$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;
});

内存使用

silent

类似于 resolve,但会捕获任何 Exception 并将其保存为第二个参数。
如果失败,承诺将以 null 解决。

$fn = function () {
    throw new \Exception('hey!');
};
$promise = Async\silent($fn, $e);
// Promise resolves with null
// $e will hold an the hey! exception

execute

异步执行命令。
返回一个包含命令输出的 Promise

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();
    });

可以传递一个以秒为单位的 timeout 参数。

sleep

一个异步的 sleep 函数。这不会阻塞其他事件。

$promise = Async\resolve(function () {
    $start = time();
    yield Async\sleep(2);
    $end = time();
    return $end-$start;
});
// $promise resolves in ~2 seconds

记住这是一个非阻塞的 sleep,如果你不在 Async\resolve 内等待它或 yield,则 Promise 将在后台解决。

$start = time();
Async\sleep(2);
$end = time();
// $start and $end will be the same

wait

也称为 sync,使异步代码阻塞。在需要使用异步库的同步/阻塞场景中使用此功能。

此函数接收以下之一:GeneratorClosurePromiseInterface

$start = time();
Async\wait(Async\sleep(2));
$end = time();
// $end == $start+2;

第二个浮点参数是秒数超时,默认无超时。

第三个浮点参数是检查的间隔,默认为 0.01 秒。间隔低将消耗更多 CPU。

async

需要在一个异步环境中运行一段阻塞代码?使用这个,但请注意它使用 pcntl_fork

第一个参数是可调用的,第二个参数是可调用参数的数组。

$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
}

同时运行的异步子进程数量有限制为 50。可以通过调用 Async\set_forks_limit 来更改此限制。
此限制也适用于 Async\execute

Async\set_forks_limit(100);
echo Async\get_forks_limit(); // 100

当达到限制时,代码将等待任何之前的子进程完成后再继续,保持异步子进程数不超过设置的子进程限制。

retry

运行一个 函数(Closure/Generator)直到 retries 次返回“良好”。否则,返回最后一个异常。
此函数还可以忽略一组异常类或消息。

$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
 */

timeout

类似于 React\Promise\Timer\timeout(),但允许 GeneratorClosure

$func = function () {
    yield Async\sleep(2);
    return true;
};
Async\wait(Async\timeout($func, 1.5));
// Throws an Exception due to the timeout 1.5 < 2

timer

保存已过微秒数(浮点数)。

Async\wait(function () {

    Async\timer(function () {
        Async\sleep(0.1);
    }, $msecs);
    print_r($msecs); // ~100ms

});

wait_memory

等待可用的一定数量的内存字节数。
这用于循环内部,以避免由于创建和留下多个 Promise 而导致的内存耗尽。

Async\wait(function () {

    $loop = 20000;
    $mem = 1024*1024*16; // 16MB
    while($loop--) {
        yield Async\waitMemory($mem);
        Async\sleep(1);
    }

});

可以传递第二个参数作为运行检查的频率。
返回剩余的字节数(memory_limit - memory_get_usage())。

rglob

具有忽略参数的递归 glob

考虑以下文件

/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
*/

PHP 文件函数

以下函数与它们的 PHP 版本具有相同的参数,但使用 Async\async 运行,并可选择将 LoopInterface 作为其第一个参数。

这些尚未经过生产测试/优化。请谨慎使用。

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

示例

$lines = Async\wait(Async\file('/etc/hosts'));
var_dump($lines);

许可

MIT,请参阅 LICENSE。