f2 / promises
一个简单易用的 Promise 和协程库,具有与 Swoole、ReactPHP、Amp 等事件循环透明协同工作的事件循环。
Requires
- f2/common: ^1.0
Requires (Dev)
- f2/asserty: ^1.0
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 */