react / promise
PHP对CommonJS Promises/A的轻量级实现
Requires
- php: >=7.1.0
Requires (Dev)
- phpstan/phpstan: 1.10.39 || 1.4.10
- phpunit/phpunit: ^9.6 || ^7.5
README
PHP对CommonJS Promises/A的轻量级实现。
目录
介绍
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()
调用中注册的处理程序做出以下保证
$onFulfilled
或$onRejected
中只有一个将被调用,永远不会同时调用。$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的Wiki和API文档移植过来。
许可
在MIT许可下发布。