piv915 / promises
Guzzle promises 库
Requires
- php: >=5.5.0
Requires (Dev)
- phpunit/phpunit: ~4.0
This package is not auto-updated.
Last update: 2024-10-02 09:51:55 UTC
README
Promises/A+ 实现可迭代处理 promise 连接和解析,允许“无限”promise 连接同时保持栈大小不变。阅读 这篇文章 了解 promises 的基本介绍。
功能
- Promises/A+ 实现。
- Promise 解析和连接处理是迭代的,允许“无限”promise 连接。
- Promises 有一个同步的
wait
方法。 - Promises 可以被取消。
- 与任何具有
then
函数的对象一起工作。 - 使用
GuzzleHttp\Promise\coroutine()
实现 C# 风格的异步/等待协程 promises。
快速开始
promise 代表异步操作最终的结果。与 promise 交互的主要方式是通过其 then
方法,它注册回调以接收 promise 的最终值或无法满足 promise 的原因。
回调
通过提供可选的 $onFulfilled
后跟可选的 $onRejected
函数来使用 then
方法注册回调。
use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise->then( // $onFulfilled function ($value) { echo 'The promise was fulfilled.'; }, // $onRejected function ($reason) { echo 'The promise was rejected.'; } );
解析 一个 promise 意味着你要么用一个 值 满足一个 promise,要么用一个 原因 拒绝一个 promise。解析一个 promises 触发与 promises 的 then
方法注册的回调。这些回调只触发一次,并且按照它们被添加的顺序。
解析一个 promise
使用 resolve($value)
方法满足 promises。除了 GuzzleHttp\Promise\RejectedPromise
之外的所有值解析 promise 都将触发所有 onFulfilled 回调(用拒绝的 promise 满足 promise 将拒绝该 promise 并触发 $onRejected
回调)。
use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise ->then(function ($value) { // Return a value and don't break the chain return "Hello, " . $value; }) // This then is executed after the first then and receives the value // returned from the first then. ->then(function ($value) { echo $value; }); // Resolving the promise triggers the $onFulfilled callbacks and outputs // "Hello, reader". $promise->resolve('reader.');
Promise 转发
Promises 可以依次连接。链中的每个 then 都是一个新的 promise。promise 的返回值是转发到链中下一个 promise 的值。在 then
回调中返回一个 promise 将导致链中的后续 promises 只在返回的 promise 被满足时才被满足。链中的下一个 promise 将使用 promise 的解析值调用。
use GuzzleHttp\Promise\Promise; $promise = new Promise(); $nextPromise = new Promise(); $promise ->then(function ($value) use ($nextPromise) { echo $value; return $nextPromise; }) ->then(function ($value) { echo $value; }); // Triggers the first callback and outputs "A" $promise->resolve('A'); // Triggers the second callback and outputs "B" $nextPromise->resolve('B');
Promise 拒绝
当 promise 被拒绝时,$onRejected
回调将使用拒绝原因调用。
use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise->then(null, function ($reason) { echo $reason; }); $promise->reject('Error!'); // Outputs "Error!"
拒绝转发
如果在 $onRejected
回调中抛出异常,后续的 $onRejected
回调将使用抛出的异常作为原因调用。
use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise->then(null, function ($reason) { throw new \Exception($reason); })->then(null, function ($reason) { assert($reason->getMessage() === 'Error!'); }); $promise->reject('Error!');
你还可以通过在 $onFulfilled
或 $onRejected
回调中返回一个 GuzzleHttp\Promise\RejectedPromise
来将拒绝沿 promise 链向下转发。
use GuzzleHttp\Promise\Promise; use GuzzleHttp\Promise\RejectedPromise; $promise = new Promise(); $promise->then(null, function ($reason) { return new RejectedPromise($reason); })->then(null, function ($reason) { assert($reason === 'Error!'); }); $promise->reject('Error!');
如果在 $onRejected
回调中没有抛出异常,并且回调没有返回一个拒绝的 promise,则将使用从 $onRejected
回调返回的值调用下游的 $onFulfilled
回调。
use GuzzleHttp\Promise\Promise; use GuzzleHttp\Promise\RejectedPromise; $promise = new Promise(); $promise ->then(null, function ($reason) { return "It's ok"; }) ->then(function ($value) { assert($value === "It's ok"); }); $promise->reject('Error!');
同步等待
您可以使用Promise的wait
方法同步强制Promise完成。在创建Promise时,您可以提供一个等待函数,用于同步强制Promise完成。当调用等待函数时,它应该向Promise提供一个值或者拒绝Promise。如果等待函数没有提供值,则会抛出异常。提供给Promise构造函数的等待函数将在调用Promise的wait
函数时被调用。
$promise = new Promise(function () use (&$promise) { $promise->deliver('foo'); }); // Calling wait will return the value of the promise. echo $promise->wait(); // outputs "foo"
如果在调用Promise的等待函数时遇到异常,Promise将被拒绝,并抛出异常。
$promise = new Promise(function () use (&$promise) { throw new \Exception('foo'); }); $promise->wait(); // throws the exception.
对已解决的Promise调用wait
不会触发等待函数。它将简单地返回之前提供的值。
$promise = new Promise(function () { die('this is not called!'); }); $promise->deliver('foo'); echo $promise->wait(); // outputs "foo"
对已拒绝的Promise调用wait
将抛出异常。如果拒绝原因是\Exception
的实例,则抛出原因。否则,将抛出GuzzleHttp\Promise\RejectionException
,可以通过调用异常的getReason
方法来获取原因。
$promise = new Promise(); $promise->reject('foo'); $promise->wait();
PHP致命错误:未捕获的异常 'GuzzleHttp\Promise\RejectionException',信息为 'Promise被拒绝,原因:foo'
解包Promise
当同步等待Promise时,您将Promise的状态加入到当前执行状态(即如果Promise已解决则返回Promise的值,如果Promise被拒绝则抛出异常)。这被称为“解包”Promise。等待Promise默认会解包Promise状态。
通过将false
传递给wait
函数的第一个参数,您可以强制Promise解决而不解包Promise状态。
$promise = new Promise(); $promise->reject('foo'); // This will not throw an exception. It simply ensures the promise has // been resolved. $promise->wait(false);
在解包Promise时,Promise提供的值将被等待,直到解包的值不再是Promise。这意味着如果您用Promise B解决Promise A并解包Promise A,等待函数返回的值将是传递给Promise B的值。
注意:如果您不解包Promise,则不返回任何值。
取消
您可以使用Promise的cancel
方法取消尚未解决的Promise。在创建Promise时,您可以提供一个可选的取消函数,当调用此函数时,将取消Promise计算解决的动作。
API
Promise
在创建Promise对象时,您可以提供可选的$waitFn
和$cancelFn
。$waitFn
是一个不带参数的函数,它被调用以解决Promise。$cancelFn
是一个不带参数的函数,它被期望取消Promise的计算。当调用Promise的cancel
方法时,它会调用。
use GuzzleHttp\Promise\Promise; $promise = new Promise( function () use (&$promise) { $promise->resolve('waited'); }, function () { // do something that will cancel the promise computation (e.g., close // a socket, cancel a database query, etc...) } ); assert('waited' === $promise->wait());
Promise有以下方法
-
then(callable $onFulfilled, callable $onRejected) : PromiseInterface
创建一个新的Promise,当Promise解决时,它会解决或拒绝。
-
wait($unwrap = true) : mixed
同步等待Promise完成。
$unwrap
控制对于已解决的Promise是否返回其值,或者如果Promise被拒绝则抛出异常。默认设置为true
。 -
cancel()
如果可能,尝试取消Promise。被取消的Promise及其尚未解决的父级祖先也将被取消。任何等待已取消Promise解决的Promise也将被取消。
-
getState() : string
返回Promise的状态。其中一个为
pending
、fulfilled
或rejected
。 -
resolve($value)
使用给定的
$value
解决Promise。 -
reject($reason)
使用给定的
$reason
拒绝Promise。
已解决的Promise
可以创建一个已解决的Promise来表示已被解决的Promise。
use GuzzleHttp\Promise\FulfilledPromise; $promise = new FulfilledPromise('value'); // Fulfilled callbacks are immediately invoked. $promise->then(function ($value) { echo $value; });
拒绝的Promise
可以创建一个已拒绝的Promise来表示已被拒绝的Promise。
use GuzzleHttp\Promise\RejectedPromise; $promise = new RejectedPromise('Error'); // Rejected callbacks are immediately invoked. $promise->then(null, function ($reason) { echo $reason; });
Promise 互操作
此库与具有then
方法的国外承诺一起工作。这意味着您可以使用 Guzzle 承诺与React 承诺等一起使用。当在国外承诺的 then 方法回调中返回承诺时,承诺解析将递归发生。
// Create a React promise $deferred = new React\Promise\Deferred(); $reactPromise = $deferred->promise(); // Create a Guzzle promise that is fulfilled with a React promise. $guzzlePromise = new \GuzzleHttp\Promise\Promise(); $guzzlePromise->then(function ($value) use ($reactPromise) { // Do something something with the value... // Return the React promise return $reactPromise; });
请注意,在转发国外承诺时,不再可能进行等待和取消链式操作。您需要将第三方承诺包裹在 Guzzle 承诺中,才能利用国外承诺的等待和取消功能。
事件循环集成
为了保持栈大小恒定,Guzzle 承诺使用任务队列异步解析。当同步等待承诺时,任务队列将自动运行,以确保阻塞承诺和任何转发的承诺被解析。当在事件循环中使用承诺异步时,您需要在循环的每个迭代中运行任务队列。如果您不运行任务队列,则承诺将不会解析。
您可以使用全局任务队列实例的run()
方法运行任务队列。
// Get the global task queue $queue = \GuzzleHttp\Promise\queue(); $queue->run();
例如,您可以使用周期性计时器将 Guzzle 承诺与 React 一起使用
$loop = React\EventLoop\Factory::create(); $loop->addPeriodicTimer(0, [$queue, 'run']);
待办事项:也许在每次迭代中添加一个futureTick()
会更快?
实现说明
承诺解析和链式处理是迭代的
通过将挂起的处理程序从一个所有者转移到另一个所有者,承诺是迭代解析的,允许进行“无限”的 then 链式操作。
<?php require 'vendor/autoload.php'; use GuzzleHttp\Promise\Promise; $parent = new Promise(); $p = $parent; for ($i = 0; $i < 1000; $i++) { $p = $p->then(function ($v) { // The stack size remains constant (a good thing) echo xdebug_get_stack_depth() . ', '; return $v + 1; }); } $parent->resolve(0); var_dump($p->wait()); // int(1000)
当承诺使用非承诺值实现或拒绝时,承诺将接管每个子承诺的处理程序,并通过不使用递归向下传递值。
当承诺使用另一个承诺实现时,原始承诺将所有挂起的处理程序转移到新承诺。当新承诺最终解析时,所有挂起的处理程序将传递转发的值。
承诺是延迟的。
一些承诺库使用延迟对象来表示计算,并使用承诺对象来表示计算结果的交付。这种计算和交付的分离很好,因为承诺的消费者无法修改最终将交付的值。
能够迭代实现承诺解析和链式操作的一个副作用是,一个承诺需要能够访问另一个承诺的状态,以在处理程序的所有权之间进行转移。为了在不使承诺的处理程序公开可变的情况下实现这一点,承诺也是延迟值,允许相同父类的承诺访问和修改相同类型承诺的私有属性。虽然这确实允许消费者修改延迟的解析或拒绝,但这是为了保持栈大小恒定所付出的微小代价。
$promise = new Promise(); $promise->then(function ($value) { echo $value; }); // The promise is the deferred value, so you can deliver a value to it. $promise->deliver('foo'); // prints "foo"