react/promise

PHP对CommonJS Promises/A的轻量级实现

资助包维护!
开放集体

安装次数: 242,463,150

依赖关系: 599

建议者: 20

安全性: 0

星标: 2,363

关注者: 52

分支: 146

开放问题: 2

v3.2.0 2024-05-24 10:39 UTC

README

PHP对CommonJS Promises/A的轻量级实现。

CI status installs on Packagist

目录

  1. 介绍
  2. 概念
  3. API
  4. 示例
  5. 安装
  6. 测试
  7. 致谢
  8. 许可

介绍

Promise是一个库,它实现了PHP的CommonJS Promises/A

它还提供了其他一些有用的承诺相关概念,例如连接多个承诺以及映射和减少承诺集合。

如果您之前从未听说过承诺,请先阅读此内容

概念

延迟

Deferred表示一个可能尚未完成的计算或工作单元。通常(但不总是),该计算将是某种异步执行并在未来的某个时间点完成的操作。

承诺

虽然deferred代表计算本身,但Promise代表该计算的结果。因此,每个deferred都有一个承诺作为其实际结果的占位符。

API

延迟

Deferred代表一个尚未解决的运算操作。它有分开的承诺和解析器部分。

$deferred = new React\Promise\Deferred();

$promise = $deferred->promise();

$deferred->resolve(mixed $value);
$deferred->reject(\Throwable $reason);

该方法返回deferred的承诺。

resolve和reject方法控制deferred的状态。

Deferred的构造函数接受一个可选的$canceller参数。有关更多信息,请参阅Promise

Deferred::promise()

$promise = $deferred->promise();

返回deferred的承诺,您可以将它交给其他人,同时保留自己修改其状态的权限。

Deferred::resolve()

$deferred->resolve(mixed $value);

通过调用$onFulfilled(通过$promise->then()注册)并传递$value来通知所有消费者,以解决promise()返回的承诺。

如果$value本身是承诺,则该承诺在解决后将过渡到该承诺的状态。

另请参阅resolve()函数

Deferred::reject()

$deferred->reject(\Throwable $reason);

拒绝promise()返回的承诺,表示deferred的计算失败。通过调用$onRejected(通过$promise->then()注册)并传递$reason来通知所有消费者。

另请参阅reject()函数

PromiseInterface

承诺接口为所有承诺实现提供了公共接口。请参阅Promise,了解此包公开的唯一公共实现。

承诺代表一个最终的结果,可能是履行(成功)及其相关值,或者是拒绝(失败)及其相关原因。

一旦承诺处于履行或拒绝状态,它就变得不可变。其状态或结果(或错误)都不能被修改。

PromiseInterface::then()

$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null);

通过将函数应用于承诺的履行或拒绝值来转换承诺的值。返回一个新的承诺,用于转换的结果。

then()方法将新的履行和拒绝处理程序注册到承诺上(所有参数都是可选的)

  • $onFulfilled将在承诺履行后调用,并将结果作为第一个参数传递。
  • $onRejected将在承诺拒绝后调用,并将原因作为第一个参数传递。

它返回一个新的承诺,将根据哪个被调用($onFulfilled$onRejected),以返回值或抛出的异常履行,或拒绝。

承诺对在then()调用中注册的处理程序做出以下保证

  1. $onFulfilled$onRejected中只有一个将被调用,永远不会同时调用。
  2. $onFulfilled$onRejected永远不会被调用多次。

另请参阅

PromiseInterface::catch()

$promise->catch(callable $onRejected);

为承诺注册拒绝处理程序。这是

$promise->then(null, $onRejected);

此外,您还可以为$onRejected$reason参数添加类型提示,以仅捕获特定错误。

$promise
    ->catch(function (\RuntimeException $reason) {
        // Only catch \RuntimeException instances
        // All other types of errors will propagate automatically
    })
    ->catch(function (\Throwable $reason) {
        // Catch other errors
    });

PromiseInterface::finally()

$newPromise = $promise->finally(callable $onFulfilledOrRejected);

允许您在承诺链中执行“清理”类型的任务。

当承诺履行或拒绝时,它安排调用$onFulfilledOrRejected,不带任何参数。

  • 如果$promise履行,并且$onFulfilledOrRejected成功返回,则$newPromise将用与$promise相同的值履行。
  • 如果$promise履行,并且$onFulfilledOrRejected抛出或返回一个拒绝的承诺,则$newPromise将用抛出的异常或拒绝承诺的原因拒绝。
  • 如果$promise拒绝,并且$onFulfilledOrRejected成功返回,则$newPromise将以与$promise相同的理由拒绝。
  • 如果$promise拒绝,并且$onFulfilledOrRejected抛出或返回一个拒绝的承诺,则$newPromise将用抛出的异常或拒绝承诺的原因拒绝。

finally()的行为类似于同步的finally语句。当与catch()结合使用时,finally允许您编写类似于熟悉的同步catch/finally对的代码。

考虑以下同步代码

try {
    return doSomething();
} catch (\Throwable $e) {
    return handleError($e);
} finally {
    cleanup();
}

类似异步代码(带有返回承诺的doSomething())可以写成

return doSomething()
    ->catch('handleError')
    ->finally('cleanup');

PromiseInterface::cancel()

$promise->cancel();

cancel()方法通知承诺的创建者不再对操作的结果感兴趣。

一旦承诺已经解决(无论是履行还是拒绝),在承诺上调用cancel()就没有效果。

PromiseInterface::otherwise()

自v3.0.0以来已弃用,请参阅catch()

otherwise()方法为承诺注册拒绝处理程序。

此方法仅为了向后兼容和简化版本间的升级而存在。它是

$promise->catch($onRejected);

PromiseInterface::always()

自v3.0.0以来已弃用,请参阅finally()

always()方法允许您在承诺链中执行“清理”类型的任务。

此方法仅为了向后兼容和简化版本间的升级而存在。它是

$promise->finally($onFulfilledOrRejected);

承诺

创建一个承诺,其状态由传递给$resolver的函数控制。

$resolver = function (callable $resolve, callable $reject) {
    // Do some work, possibly asynchronously, and then
    // resolve or reject.

    $resolve($awesomeResult);
    // or throw new Exception('Promise rejected');
    // or $resolve($anotherPromise);
    // or $reject($nastyError);
};

$canceller = function () {
    // Cancel/abort any running operations like network connections, streams etc.

    // Reject promise by throwing an exception
    throw new Exception('Promise cancelled');
};

$promise = new React\Promise\Promise($resolver, $canceller);

Promise 构造函数接收一个解析器函数和一个可选的取消器函数,这两个函数都将使用两个参数被调用

  • $resolve($value) - 主要函数,决定了返回的 Promise 的命运。接受一个非 Promise 值或另一个 Promise。当用非 Promise 值调用时,用该值实现 Promise。当用另一个 Promise 调用,例如 $resolve($otherPromise),Promise 的命运将与 $otherPromise 的命运相同。
  • $reject($reason) - 用于拒绝 Promise 的函数。建议直接抛出异常而不是使用 $reject()

如果解析器或取消器抛出异常,Promise 将以抛出的异常作为拒绝原因被拒绝。

解析器函数将被立即调用,取消器函数只会在所有消费者都调用了 Promise 的 cancel() 方法之后调用一次。

函数

用于创建和连接 Promise 集合的有用函数。

所有在 Promise 集合上工作的函数(如 all()race() 等)都支持取消。这意味着,如果您在返回的 Promise 上调用 cancel(),则集合中的所有 Promise 都将被取消。

resolve()

$promise = React\Promise\resolve(mixed $promiseOrValue);

为提供的 $promiseOrValue 创建一个 Promise。

如果 $promiseOrValue 是一个值,它将是返回 Promise 的解析值。

如果 $promiseOrValue 是一个 thenable(任何提供 then() 方法的对象),则返回一个跟踪 thenable 状态的可信 Promise。

如果 $promiseOrValue 是一个 Promise,则将其原样返回。

结果 $promise 实现了 PromiseInterface,可以像任何其他 Promise 一样被消费

$promise = React\Promise\resolve(42);

$promise->then(function (int $result): void {
    var_dump($result);
}, function (\Throwable $e): void {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

reject()

$promise = React\Promise\reject(\Throwable $reason);

为提供的 $reason 创建一个拒绝的 Promise。

注意,PHP 7 中引入的 \Throwable 接口涵盖了用户空间 \Exception 和 PHP 内部错误 \Error。通过强制使用 \Throwable 作为拒绝 Promise 的原因,可以使用任何语言错误或用户空间异常来拒绝 Promise。

结果 $promise 实现了 PromiseInterface,可以像任何其他 Promise 一样被消费

$promise = React\Promise\reject(new RuntimeException('Request failed'));

$promise->then(function (int $result): void {
    var_dump($result);
}, function (\Throwable $e): void {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

注意,拒绝的 Promise 应该像任何异常一样在 try + catch 块中被处理。如果您删除未处理的拒绝 Promise 的最后一个引用,它将报告一个未处理的 Promise 拒绝

function incorrect(): int
{
     $promise = React\Promise\reject(new RuntimeException('Request failed'));

     // Commented out: No rejection handler registered here.
     // $promise->then(null, function (\Throwable $e): void { /* ignore */ });

     // Returning from a function will remove all local variable references, hence why
     // this will report an unhandled promise rejection here.
     return 42;
}

// Calling this function will log an error message plus its stack trace:
// Unhandled promise rejection with RuntimeException: Request failed in example.php:10
incorrect();

如果用 then() 方法catch() 方法finally() 方法 捕获拒绝原因,则拒绝的 Promise 将被视为“已处理”。请注意,这些方法中的每一个都返回一个新的 Promise,该 Promise 可能会再次被拒绝,如果您重新抛出异常。

如果用 cancel() 方法 终止操作(这通常会拒绝仍在挂起的 Promise),则拒绝的 Promise 也将被视为“已处理”。

另请参阅 set_rejection_handler() 函数

all()

$promise = React\Promise\all(iterable $promisesOrValues);

返回一个 Promise,只有当 $promisesOrValues 中的所有项目都已解析时才会解析。返回 Promise 的解析值将是一个包含 $promisesOrValues 中每个项目解析值的数组。

race()

$promise = React\Promise\race(iterable $promisesOrValues);

启动一场竞争赛跑,允许一个赢家。返回一个 Promise,其解析方式与第一个解决的 Promise 相同。

如果 $promisesOrValues 包含 0 个项目,则返回的 Promise 将变成 无限挂起

any()

$promise = React\Promise\any(iterable $promisesOrValues);

返回一个在 $promisesOrValues 中的任何项解析时解决的 Promise。返回的 Promise 的解析值将是触发项的解析值。

如果 $promisesOrValues 中的所有项都拒绝,则返回的 Promise 才会拒绝。拒绝值将是一个 React\Promise\Exception\CompositeException,它包含所有拒绝原因。可以通过 CompositeException::getThrowables() 获取拒绝原因。

如果 $promisesOrValues 包含 0 项,则返回的 Promise 也会拒绝一个 React\Promise\Exception\LengthException

set_rejection_handler()

React\Promise\set_rejection_handler(?callable $callback): ?callable;

设置全局未处理的 Promise 拒绝处理程序。

请注意,拒绝的 Promise 应始终像在任何 try + catch 块中捕获任何异常一样处理。如果您删除未处理的拒绝 Promise 的最后一个引用,它将报告未处理的 Promise 拒绝。有关更多信息,请参阅 reject() 函数

?callable $callback 参数必须是一个有效的回调函数,该函数接受一个 Throwable 参数或一个 null 值以恢复默认的 Promise 拒绝处理程序。回调函数的返回值将被忽略并且没有效果,因此您应该返回一个 void 值。回调函数不得抛出,否则程序将以致命错误终止。

该函数返回上一个拒绝处理程序或使用默认的 Promise 拒绝处理程序时的 null

默认的 Promise 拒绝处理程序将记录一个错误消息及其堆栈跟踪。

// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
React\Promise\reject(new RuntimeException('Unhandled'));

Promise 拒绝处理程序可用于自定义日志消息或将日志写入自定义目标。一般来说,此函数仅应作为最后的手段使用,并且最好使用 then() 方法catch() 方法finally() 方法 来处理 Promise 拒绝。有关更多信息,请参阅 reject() 函数

示例

如何使用Deferred

function getAwesomeResultPromise()
{
    $deferred = new React\Promise\Deferred();

    // Execute a Node.js-style function using the callback pattern
    computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) {
        if ($error) {
            $deferred->reject($error);
        } else {
            $deferred->resolve($result);
        }
    });

    // Return the promise
    return $deferred->promise();
}

getAwesomeResultPromise()
    ->then(
        function ($value) {
            // Deferred resolved, do something with $value
        },
        function (\Throwable $reason) {
            // Deferred rejected, do something with $reason
        }
    );

承诺转发的工作原理

以下是一些简单的示例,说明 Promises/A 传真的工作原理。当然,这些示例是虚构的,在实际使用中,Promise 链通常会跨越几个函数调用,甚至跨越您的应用程序架构的几个级别。

决议转发

已解析的 Promise 将解析值转发到下一个 Promise。第一个 Promise,$deferred->promise(),将使用以下 $deferred->resolve() 传递的值进行解析。

then() 的每次调用都会返回一个新的 Promise,该 Promise 将解析为前一个处理程序的返回值。这创建了一个 Promise "管道"。

$deferred = new React\Promise\Deferred();

$deferred->promise()
    ->then(function ($x) {
        // $x will be the value passed to $deferred->resolve() below
        // and returns a *new promise* for $x + 1
        return $x + 1;
    })
    ->then(function ($x) {
        // $x === 2
        // This handler receives the return value of the
        // previous handler.
        return $x + 1;
    })
    ->then(function ($x) {
        // $x === 3
        // This handler receives the return value of the
        // previous handler.
        return $x + 1;
    })
    ->then(function ($x) {
        // $x === 4
        // This handler receives the return value of the
        // previous handler.
        echo 'Resolve ' . $x;
    });

$deferred->resolve(1); // Prints "Resolve 4"

拒绝转发

拒绝的 Promise 的工作方式与此类似,并且也像 try/catch 一样工作:当你捕获一个异常时,你必须重新抛出才能使其传播。

同样,当你处理一个拒绝的 Promise 时,为了传播拒绝,可以通过返回一个拒绝的 Promise 或实际上抛出(因为 Promise 将抛出的异常转换为拒绝)来“重新抛出”。

$deferred = new React\Promise\Deferred();

$deferred->promise()
    ->then(function ($x) {
        throw new \Exception($x + 1);
    })
    ->catch(function (\Exception $x) {
        // Propagate the rejection
        throw $x;
    })
    ->catch(function (\Exception $x) {
        // Can also propagate by returning another rejection
        return React\Promise\reject(
            new \Exception($x->getMessage() + 1)
        );
    })
    ->catch(function ($x) {
        echo 'Reject ' . $x->getMessage(); // 3
    });

$deferred->resolve(1);  // Prints "Reject 3"

混合决议和拒绝转发

就像 try/catch 一样,你可以选择是否传播。解析和拒绝的混合将以可预测的方式转发处理程序结果。

$deferred = new React\Promise\Deferred();

$deferred->promise()
    ->then(function ($x) {
        return $x + 1;
    })
    ->then(function ($x) {
        throw new \Exception($x + 1);
    })
    ->catch(function (\Exception $x) {
        // Handle the rejection, and don't propagate.
        // This is like catch without a rethrow
        return $x->getMessage() + 1;
    })
    ->then(function ($x) {
        echo 'Mixed ' . $x; // 4
    });

$deferred->resolve(1);  // Prints "Mixed 4"

安装

安装此库的推荐方法是 通过 Composer对 Composer 不熟悉?

此项目遵循 SemVer。这将安装此分支的最新支持版本。

composer require react/promise:^3.2

有关版本升级的详细信息,请参阅 CHANGELOG

此项目旨在在任何平台上运行,因此不需要任何 PHP 扩展,并支持在 PHP 7.1 到当前 PHP 8+ 上运行。强烈建议使用此项目的最新支持版本。

我们致力于提供长期支持(LTS)选项,并提供平滑的升级路径。如果您正在使用较旧的PHP版本,可以使用2.x分支(PHP 5.4+)或1.x分支(PHP 5.3+),这两个分支都提供兼容的API,但不会利用较新的语言特性。您可以同时针对多个版本进行目标定位,以支持更广泛的PHP版本,如下所示

composer require "react/promise:^3 || ^2 || ^1"

测试

要运行测试套件,您首先需要克隆此仓库,然后通过Composer安装所有依赖项(Composer)

composer install

要运行测试套件,请进入项目根目录并运行

vendor/bin/phpunit

此外,我们还使用PHPStan的最高级别来确保项目中的类型安全

vendor/bin/phpstan

致谢

Promise是由Brian Cavalier移植自when.js

此外,大部分文档已从when.js的WikiAPI文档移植过来。

许可

在MIT许可下发布。