co/loop

一个稳健的事件循环API,用于编写可以在Amp或React、Slim、CodeIgniter、Laravel和Symfony等框架间工作的应用程序。

1.0.108 2022-06-27 23:10 UTC

README

一个事件循环API,用于在传统PHP代码中编写异步代码,同时也可以与React或Amp等常见的异步框架无缝协作。

Moebius Loop提供了一种优雅且一致的API,用于处理非阻塞I/O,在传统的同步PHP代码和完全异步的框架(如React或Amp)中均能良好工作。

Laravel示例

Moebius\Loop可以与Laravel或Symfony等大多数框架一起使用。使用这些框架中的异步代码的挑战是,框架不会等待你的承诺完成。

解决方案是使用Moebius\Loop::await($promise)函数。只要所有异步代码都使用Moebius\Loop直接编写,或者为React或Amp编写,就可以使用Moebius\Loop来解决承诺。

<?php
namespace App\Http\Controllers;
 
use Moebius\Loop;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Profile;
 
class UserController extends Controller
{
    public function show($id)
    {
        /**
         * Go get a performance boost:
         *
         * We MUST use async functions which return a promise,
         * and internally use the event-loop API to wait for
         * IO resources to become readable or writable.
         */
        $user = User::asyncFindOrFail($id);
        $profile = Profile::asyncFindOrFail($id);

        /**
         * Also we SHOULD send off as many async calls as possible
         * before using the `Loop::await()` function to resolve
         * these promises.
         *
         * Both promises we created above will be running while you
         * await the `$user` promise. The `$profile` promise may
         * even finish first - but that does not matter below:
         */
        return view('user.profile', [
            'user' => Loop::await($user),
            'profile' => Loop::await($profile),
        ]);
    }
}

如您所见,在任意框架中都可以轻松使用异步代码。

API参考

Moebius\Loop::getTime()

获取当前事件循环时间。这是一个从任意时间点开始的秒数。

Moebius\Loop::await(object $promise, float $timeout=null): mixed

运行事件循环,直到承诺解决或超时。

Moebius\Loop::run(Closure $shouldResumeFunc=null): void

运行事件循环,直到调用Moebius\Loop::stop()。如果提供了回调,则循环将保持运行,直到回调返回一个falsey值。

Moebius\Loop::delay(float $time, Closure $callback): Closure

$time秒后运行回调。返回的回调将取消定时器。

Moebius\Loop::interval(float $interval, Closure $callback): Closure

$interval秒后运行回调,并每$interval秒重复一次回调。返回的回调将取消间隔。

Moebius\Loop::readable(resource $resource, Closure $callback): Closure

在每次tick时运行回调,只要从流资源中读取不会阻塞。这也可以用来在服务器套接字上接受新的连接。返回的回调将取消监视流。

Moebius\Loop::writable(resource $resource, Closure $callback): Closure

在每次tick时运行回调,只要向流资源写入不会阻塞。返回的回调将取消监视流。

Moebius\Loop::read(resource $resource, Closure $callback): Closure

从流资源中读取数据块,并用读取的数据调用$callback。此函数将在EOF调用时停止,除非首先调用返回的回调来取消读取操作。

Moebius\Loop::signal(int $signalNumber, Closure $callback): Closure

每当进程收到信号时运行回调。回调将在每次收到信号时继续调用,直到调用返回的回调。

示例

一个原始示例,说明如何使用Moebius\Loop编写异步代码。

此代码将在Amp和React上运行,如果你在Laravel或Symfony等更传统的环境中使用。

<?php
require('vendor/autoload.php');

/**
 * This function returns a Promise about something that will
 * be available eventually. It will immediately return a Promise
 * object which is a *promise about a future value*.
 */
function async_read_file(string $filename) {
    return new Moebius\Promise(function($ready, $failure) use ($filename) {
        // Open the file in read non-blocking mode
        $fp = fopen($filename, 'rn');

        // Wait for the file to become readable
        Moebius\Loop::readable($fp, function($fp) use ($ready) {

            // Call the $ready callback with the value
            $ready(stream_get_contents($fp));

            // Close the file
            fclose($fp);

        });
    });    
}

/**
 * Now we will read two files in parallel using our above function.
 */
$file1 = async_read_file('file-1.txt');
$file2 = async_read_file('file-2.txt');

/**
 * ALTERNATIVE 1
 * 
 * The traditional way of waiting for results from a promise is via 
 * the "then" method. This can lead to the well known "callback hell".
 */
$file1->then(function($contents) {
    echo "FILE 1: ".$contents."\n\n";
}, function($error) {
    echo "Failed to read file 1\n";
});
$file2->then(function($contents) {
    echo "FILE 2: ".$contents."\n\n";
}, function($error) {
    echo "Failed to read file 1\n";
});


/**
 * ALTERNATIVE 2
 *
 * A much easier approach to waiting for promises is to use the
 * `Moebius\Loop::await()` function. It will block your application,
 * while allowing promises to run - until the promise is fulfilled
 * or rejected.
 */
echo "FILE 1: ".Moebius\Loop::await($file1);
echo "FILE 2: ".Moebius\Loop::await($file2);

整个API

Moebius\Loop::defer(callable $callback)将安排回调在下一个事件循环中执行。

Moebius\Loop::queueMicrotask(callable $callback)将安排一个回调尽可能快地执行——在所有延迟回调之前。

Moebius\Loop::delay(float $time, callable $callback): EventHandle 将安排回调在稍后执行。

Moebius\Loop::readable(resource $fd, callable $callback): EventHandle 将安排回调在 $fd 可读时执行。

Moebius\Loop::writable(resource $fd, callable $callback): EventHandle 将安排回调在 $fd 可写时执行。

Moebius\Loop::signal(int $signum, callable $callback): EventHandle 将安排回调在应用程序收到信号时执行。

Moebius\Loop::run(callable $keepRunningFunc=null): void 将运行事件循环,直到 $keepRunningFunc 返回 false 或事件循环为空。

EventHandle 类

当订阅事件(IO、定时器、间隔或信号)时,您将接收到一个 EventHandle 类。这个句柄可以用来暂停或取消事件监听器。

示例

    $readable = Moebius\Loop::readable($fp, function($fp) {
        // read stream
    });

    // Disable listening for events
    $readable->suspend();

    // Enable listening for events
    $readable->resume();

    // Cancel listening for events (can't be resumed)
    $readable->cancel();