raimx/promise-timer

基于ReactPHP的一个简单的Promise超时实现。

v1.5.1 2019-03-27 18:10 UTC

This package is auto-updated.

Last update: 2021-08-29 02:02:29 UTC


README

Build Status

基于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文件