raimx / promise-timer
基于ReactPHP的一个简单的Promise超时实现。
Requires
- php: >=5.3
- react/event-loop: ^1.0 || ^0.5 || ^0.4 || ^0.3.5
- react/promise: ^2.7.0 || ^1.2.1
Requires (Dev)
- phpunit/phpunit: ^6.4 || ^5.7 || ^4.8.35
README
基于ReactPHP的一个简单的Promise超时实现。
目录
使用方法
这个轻量级库只包含几个简单的函数。所有函数都位于React\Promise\Timer
命名空间下。
以下示例假设您使用类似的导入语句
use React\Promise\Timer; Timer\timeout(…);
或者,您也可以使用它们的完全限定名称来引用它们
\React\Promise\Timer\timeout(…);
timeout()
timeout(PromiseInterface $promise, $time, LoopInterface $loop)
函数可以用来取消耗时太长的操作。您需要传入一个表示待处理操作的输入$promise
和超时参数。它返回一个新的具有以下解决行为的Promise
:
- 如果输入的
$promise
在$time
秒之前解决,则使用其履行值解决结果承诺。 - 如果输入的
$promise
在$time
秒之前拒绝,则使用其拒绝值拒绝结果承诺。 - 如果输入的
$promise
在$time
秒之前没有解决,则取消操作,并使用TimeoutException
拒绝结果承诺。
内部,给定的$time
值将用于启动一个计时器,该计时器将在触发时取消挂起的操作。这意味着,如果您传递一个非常小(或负)的值,它仍然会启动一个计时器,因此将在未来最早可能的时间触发。
如果输入的$promise
已经解决,则结果承诺将立即解决或拒绝,而无需启动计时器。
仅处理已解决值的常见用例如下所示
$promise = accessSomeRemoteResource(); Timer\timeout($promise, 10.0, $loop)->then(function ($value) { // the operation finished within 10.0 seconds });
一个更完整的示例可能如下所示
$promise = accessSomeRemoteResource(); Timer\timeout($promise, 10.0, $loop)->then( function ($value) { // the operation finished within 10.0 seconds }, function ($error) { if ($error instanceof Timer\TimeoutException) { // the operation has failed due to a timeout } else { // the input operation has failed due to some other error } } );
或者如果您使用的是react/promise v2.2.0或更高版本
Timer\timeout($promise, 10.0, $loop) ->then(function ($value) { // the operation finished within 10.0 seconds }) ->otherwise(function (Timer\TimeoutException $error) { // the operation has failed due to a timeout }) ->otherwise(function ($error) { // the input operation has failed due to some other error }) ;
超时取消
如上所述,timeout()
函数将取消耗时太长的底层操作。这意味着您可以确信结果承诺将随后被拒绝,并带有TimeoutException
。
然而,底层输入$promise
的情况要复杂一些:一旦计时器触发,我们将尝试在输入的$promise
上调用$promise->cancel()
,这反过来又会调用其取消处理程序。
这意味着实际上是由输入 $promise
来处理 取消支持。
-
一个常见的用例包括清理任何资源,例如打开的网络套接字、文件句柄、终止外部进程或定时器。
-
如果给定的输入
$promise
不支持取消,则这是一个无操作(NO-OP)。这意味着虽然生成的承诺仍然会被拒绝,但底层的输入$promise
可能仍然挂起,因此可以继续消耗资源。
有关取消处理程序的更多详细信息,请参阅以下章节。
取消处理程序
例如,上述操作的实现可能如下所示
function accessSomeRemoteResource() { return new Promise( function ($resolve, $reject) use (&$socket) { // this will be called once the promise is created // a common use case involves opening any resources and eventually resolving $socket = createSocket(); $socket->on('data', function ($data) use ($resolve) { $resolve($data); }); }, function ($resolve, $reject) use (&$socket) { // this will be called once calling `cancel()` on this promise // a common use case involves cleaning any resources and then rejecting $socket->close(); $reject(new \RuntimeException('Operation cancelled')); } ); }
在这个例子中,调用 $promise->cancel()
将会调用已注册的取消处理程序,然后关闭网络套接字并拒绝 Promise
实例。
如果未将取消处理程序传递给 Promise
构造函数,则调用其 cancel()
方法实际上是一个无操作。这意味着它可能仍然挂起,因此可以继续消耗资源。
有关承诺取消的更多详细信息,请参阅Promise 文档。
输入取消
无论超时处理如何,您也可以在任何时候显式地 cancel()
输入 $promise
。这意味着 timeout()
处理不会影响输入 $promise
的取消,如下例所示
$promise = accessSomeRemoteResource(); $timeout = Timer\timeout($promise, 10.0, $loop); $promise->cancel();
注册的 取消处理程序 负责处理 cancel()
调用
- 如上所述,一个常见的用途是资源清理,然后会 拒绝
Promise
。如果输入$promise
正在被拒绝,则超时会被取消,生成的承诺也将被拒绝。 - 如果输入
$promise
仍然挂起,则超时将继续运行,直到定时器到期。如果输入$promise
未注册 取消处理程序,也会发生相同的情况。
输出取消
同样,您也可以像这样显式地 cancel()
生成的承诺
$promise = accessSomeRemoteResource(); $timeout = Timer\timeout($promise, 10.0, $loop); $timeout->cancel();
注意这与上面的 输入取消 例子看起来非常相似。相应地,它们的行为也非常相似。
在生成的承诺上调用 cancel()
仅仅尝试取消输入 $promise
。这意味着我们不承担结果的责任,取消处理完全由输入 $promise
负责。
注册的 取消处理程序 负责处理 cancel()
调用
- 如上所述,一个常见的用途是资源清理,然后会 拒绝
Promise
。如果输入$promise
正在被拒绝,则超时会被取消,生成的承诺也将被拒绝。 - 如果输入
$promise
仍然挂起,则超时将继续运行,直到定时器到期。如果输入$promise
未注册 取消处理程序,也会发生相同的情况。
再次强调,在生成的承诺上调用 cancel()
仅仅尝试取消输入 $promise
。然后,由输入承诺的取消处理程序来确定承诺。如果输入承诺在超时发生时仍然挂起,则将触发正常的 超时取消 处理,实际上会拒绝输出承诺并抛出 TimeoutException
。
这样做是为了与超时取消处理保持一致,并且假设这种情况经常被这样使用。
$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop); $timeout->cancel();
如上所述,这个示例按预期工作,并清理了为输入$promise
分配的任何资源。
请注意,如果给定的输入$promise
不支持取消,则这是无效操作。这意味着虽然结果promise在超时后仍然会被拒绝,但底层的输入$promise
可能仍然处于挂起状态,因此可以继续消耗资源。
集合
如果您想等待多个promise解析,可以使用以下正常的promise原语
$promises = array( accessSomeRemoteResource(), accessSomeRemoteResource(), accessSomeRemoteResource() ); $promise = \React\Promise\all($promises); Timer\timeout($promise, 10, $loop)->then(function ($values) { // *all* promises resolved });
这适用于所有promise集合原语,例如all()
、race()
、any()
、some()
等。
有关promise原语的更多详细信息,请参阅Promise文档。
resolve()
resolve($time, LoopInterface $loop)
函数可以用来创建一个新的Promise,它在$time
秒后解析,以$time
作为履行值。
Timer\resolve(1.5, $loop)->then(function ($time) { echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL; });
内部,给定的$time
值将用来启动一个计时器,一旦触发就会解析promise。这意味着如果您传递一个非常小(或负数)的值,它仍然会启动一个计时器,因此会在未来的最早可能时间触发。
解析取消
您可以在任何时候显式地cancel()
生成的计时器promise。
$timer = Timer\resolve(2.0, $loop); $timer->cancel();
这将中止计时器,并使用RuntimeException
进行拒绝。
reject()
reject($time, LoopInterface $loop)
函数可以用来创建一个新的Promise,它在$time
秒后拒绝,并带有TimeoutException
。
Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) { echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL; });
内部,给定的$time
值将用来启动一个计时器,一旦触发就会拒绝promise。这意味着如果您传递一个非常小(或负数)的值,它仍然会启动一个计时器,因此会在未来的最早可能时间触发。
此函数补充了resolve()
函数,并可以用作高级promise消费者的基本构建块。
拒绝取消
您可以在任何时候显式地cancel()
生成的计时器promise。
$timer = Timer\reject(2.0, $loop); $timer->cancel();
这将中止计时器,并使用RuntimeException
进行拒绝。
TimeoutException
TimeoutException
扩展了PHP的内置RuntimeException
。
可以使用getTimeout()
方法获取以秒为单位的超时值。
安装
推荐通过Composer安装此库。Composer新手?
此项目遵循SemVer。这将安装最新的支持版本
$ composer require react/promise-timer:^1.5
有关版本升级的详细信息,请参阅变更日志。
本项目的目标是能够在任何平台上运行,因此不需要任何PHP扩展,并支持在旧版PHP 5.3到当前PHP 7+以及HHVM上运行。强烈推荐使用PHP 7+进行此项目。
测试
要运行测试套件,您首先需要克隆此仓库,然后通过Composer安装所有依赖项 (Composer)
$ composer install
要运行测试套件,请进入项目根目录并运行
$ php vendor/bin/phpunit
许可
MIT许可,请参阅 LICENSE文件。