f2/promises

一个简单易用的 Promise 和协程库,具有与 Swoole、ReactPHP、Amp 等事件循环透明协同工作的事件循环。

1.0.5 2020-03-15 01:18 UTC

This package is auto-updated.

Last update: 2024-09-09 05:16:09 UTC


README

f2/promises 是一个小型库,它使得构建与其他事件循环实现(如 React、Amp、Swoole)集成的便携式库变得简单;它还内置了事件循环,因此您的库可以在更多传统框架(如 Laravel)中工作。

Promise 实现

我们已经尝试使 Promise 实现与 React、Guzzle、Amp 和 php-http 的 Promise 兼容。即使您不编写异步代码,也应该能够使用 Promise 实现。

示例

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

use function F2\{defer, sleep, readable, writable};

defer(function() {

    /**
     * yield a stream resource, and f2/promises will transparently use stream_select() to
     * detect when the coroutine should continue running.
     */
    $fp = yield fopen('/path/to/file.php', 'rb');
    while (!feof($fp)) {
        echo fread(yield $fp);
    }

    /**
     * yield a thenable/promise and f2/promises will behind the scenes wait until the promise
     * is resolved before continuing the coroutine.
     */
    yield sleep(5);
    echo "This happens after 5 seconds.\n";

    /**
     * Nested coroutines is allowed. If you use yield, then the parent coroutine will pause
     */
    $result = yield defer(function() {
        yield sleep(0.5);
        return 42;
    });

    echo $result."\n"; // Prints "42"

    /**
     * Nested coroutines that you don't wait for
     */
    defer(function() {
        yield sleep(0.3);
    }).then(function($result) {
        echo $result." is resolved asynchronously";
    });
});

与 React 一起使用

F2\setup([ $loop, 'futureTick' ]);

与 Amp 一起使用

F2\setup([ \Amp\Loop::class, 'defer' ]);

与 Swoole 一起使用

F2\setup([ \Swoole\Event::class, 'defer' ]);

事件循环

Promises 是一个设计在事件循环(如 JavaScript、React 和 Amp)中运行的创新。由于大多数软件都是在没有事件循环的情况下设计的,因此我们实现了一个回退事件循环,它应该非常高效,并且可以与大多数现有框架一起使用。

理由

人们正在编写与 React、Amp 或 Swoole 一起工作的出色的异步库。特别是,我们有许多异步 DNS 解析器、MySQL 客户端、HTTP 客户端等等,但它们要么是为了使用它们自己的事件循环而设计的(嘿,Guzzle),要么是为了支持 React Amp。

这个库是尝试统一 PHP 异步库开发的尝试。

函数参考

F2\setup(callable $deferImplementation): void

提供一个函数,该函数将在事件循环中排队一个可调用项。这是我们要求的全部内容,以进行集成。在此基础上,我们可以提供异步 I/O、计时器以及其他最重要的函数。

F2\defer(callable $closure, ...$args): F2\Promise\PromiseInterface

将闭包添加到事件循环中。

在其他环境中的等效项

  • React\EventLoop\LoopInterface::futureTick().
  • Amp\Loop::defer().
  • javascript 中的 setTimeout(closure, 0)

F2\queueMicrotask(callable $closure, ...$args): void

将闭包添加到事件循环中,使其在所有其他事件循环之前运行。用于评估 Promise 的 resolve/reject 方法等。

注意 queueMicrotask() 在 React 和 Amp 中模拟此功能。这意味着如果您直接使用它们的事件循环来延迟任务,则您的微任务可能会在常规作业之后被调用。如果您保持一致性并使用这些函数,您应该不会注意到任何问题。

在其他环境中的等效项

  • javascript 中的 queueMicrotask(closure)

F2\sleep(float $duration): F2\Promise\PromiseInterface

休眠 $duration 秒。此调用可以使用 yield,或者您可以使用 then() 添加回调。

    F2\sleep(0.5)->then(function() {
        echo "0.5 seconds later...\n";
    });
// or
    yield F2\sleep(0.5);
    echo "0.5 seconds later...\n";

在其他环境中的等效项

  • React\EventLoop\LoopInterface::addTimer(float $duration, callable $closure)
  • Amp\Loop::delay(int $milliseconds, callable $closure)
  • javascript 中的 setTimeout(closure, milliseconds)

F2\readable(resource $fp): F2\Promise\PromiseInterface

等待读取流不再阻塞。

    F2\readable($fp)->then(function() use ($fp) {
        $data = fread($fp);
    });

// or

    yield F2\readable($fp);
    $data = fread($fp);

// f2/promises will automatically infer if you're waiting to read or write the stream

    $data = fread(yield $fp);

取消

    F2\cancelReadable($fp)
// or
    F2\cancel($fp) 
// or
    $promise->cancel()

在其他环境中的等效项

  • React\EventLoop\LoopInterface::addReadStream(resource $fp, callable $closure)
  • Amp\Loop::onReadable(resource $fp, callable $closure)

F2\writable(resource $fp): F2\Promise\PromiseInterface

等待写入流不再阻塞。相当于上面的 F2\readable() 示例。

取消

    F2\cancelReadable($fp)
// or
    F2\cancel($fp) 
// or
    $promise->cancel()

在其他环境中的等效项

  • React\EventLoop\LoopInterface::addWriteStream(resource $fp, callable $closure)
  • Amp\Loop::onReadable(resource $fp, callable $closure)

还有更多计划

非阻塞函数

PHP 缺乏插件和非阻塞功能,尤其是在 PHP 没有线程安全(ZTS)编译时,这是一个问题。为了解决这个问题,我们将创建一个库,包含标准 PHP 函数的非阻塞变体。

  • F2\fopen() 与流封装器。在大多数情况下,使用流封装器时,此函数是阻塞的。当打开普通文件时,可以使用 $fp = yield fopen($path, 'rbn')(注意'n'模式,表示非阻塞。
  • F2\file_get_contents() 用于本地文件和流封装器。
  • F2\file_put_contents() 用于本地文件和流封装器。

我们将考虑各种后端以启用这些功能。最可能的解决方案是启动一个 php 实例池,以异步方式执行操作。

更多示例

协程

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

defer(function() {
    while (true) {
        echo "+";
        yield;
    }
});
defer(function() {
    while (true) {
        echo "-";
        yield;
    }
});

// Outputs +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...

使用 Promise 类

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

use F2\Promise\Promise;

/**
 * Use the promise, and resolve it directly
 */
$promise = new Promise();
$promise->then(function($result) {
    echo "Result: ";
    var_dump($result);
});
$promise->resolve("Some result");

/**
 * Use the promise, and add the resolution function to the event loop
 */
$promise = new Promise(function($success, $failure) {
    $success("Some other result");
});
$promise->then(function($result) {
    echo "Result: ";
    var_dump($result);
});

协程和异步 I/O

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

use function F2{defer, readable, writable, sleep};

$t = microtime(true);

/**

  • 创建一个协程 */ defer(function() { echo t()."Hello from the CoRoutine!\n";

    // 不使用 ->then($callback) - 而是直接 yield for ($i = 0; $i < 100; $i++) {

     yield sleep(0.01);
    

    }

    echo t()."Slept for approximately 1 seconds\n"; });

/**

  • 创建另一个协程来写入文件 */ defer(function() { echo t()."A coroutine using file I/O!\n";

    // 'n' 修饰符是未记录的,可能在所有平台上都无法工作 $fp = yield writable(fopen('/tmp/some-file.php', 'wbn'));

    for ($i = 2000; $i >= 1; $i--) {

     fwrite( yield readable($fp), "$i bottles of beer on the wall, $i bottles of beer...\n" );
    

    }

    echo t()."Done writing the file!\n"; });

function t() {

global $t;

return round((microtime(true) - $t) * 1000)." ms ";

}

/** 输出

0 ms Hello from the CoRoutine! 0 ms A coroutine using file I/O! 38 ms Done writing the file! 1001 ms Slept for approximately 1 seconds */