ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Guzzle Promises 完全指南:ShowDoc 依赖树中的 Promises/A+ 实现与 PHP 异步编程实战

Guzzle Promises 完全指南:ShowDoc 依赖树中的 Promises/A+ 实现与 PHP 异步编程实战 Guzzle Promises 完全指南ShowDoc 依赖树中的 Promises/A 实现与 PHP 异步编程实战【免费下载链接】showdocShowDoc is a tool greatly applicable for an IT team to share documents online一个非常适合IT团队的在线API文档、技术文档工具项目地址: https://gitcode.com/gh_mirrors/sh/showdocGuzzle Promises 是一套完整实现 [Promises/A 规范]的 PHP Promise 库它以迭代方式处理 Promise 的解析与链式调用从而在任意长度的then链下保持栈深度恒定。在 ShowDoc 开源项目中该库以 Composer 依赖的形式被收录于 server/vendor/guzzlehttp/promises随 Guzzle HTTP 客户端server/vendor/guzzlehttp/guzzle一并引入并被 qcloud/cos-sdk-v5 等第三方 SDK 间接使用。读完本文你将掌握 Promise 的创建、解析、拒绝、转发、同步等待、取消与互操作等全部核心能力并理解其栈深度恒定的底层迭代实现原理。一、库的定位与在 ShowDoc 中的角色在 ShowDoc 仓库中Guzzle Promises 并非业务代码直接引用的模块而是作为 HTTP 客户端生态的底层基础设施存在。从其 composer.json 可以看到包名guzzlehttp/promises仅要求php 5.5本身零外部运行时依赖通过 PSR-4 规则将GuzzleHttp\Promise\命名空间映射到src/目录通过files配置自动加载 src/functions_include.php以兼容函数式 API。仓库内的 server/vendor/guzzlehttp/guzzleHTTP 客户端、server/vendor/qcloud/cos-sdk-v5对象存储 SDK以及 Symfony HTTP 客户端组件均大量引用GuzzleHttp\Promise命名空间。因此理解本库对排查上传、下载等异步任务的执行顺序与错误处理至关重要。二、特性总览Guzzle Promises 提供如下核心能力对应 README.md 的 Features 一节完整的Promises/A实现Promise 的解析与链式调用迭代化处理支持无限级then链且栈深度恒定Promise 内置同步wait方法支持**取消cancel**操作可与任何实现了then方法的对象外部 thenable协同工作提供C# 风格 async/await 协程Promise通过GuzzleHttp\Promise\Coroutine::of()实现。三、快速上手Promise 基础用法Promise代表一个异步操作的最终结果。与 Promise 交互的主要方式是通过then方法注册回调回调将收到 Promise 的最终值fulfillment value或无法兑现的原因rejection reason。3.1 注册回调通过then方法注册回调依次传入可选的$onFulfilled与可选的$onRejected函数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.; } );解析Resolving一个 Promise 意味着用一个value兑现fulfill它或用一个reason拒绝reject它。解析会触发通过then注册的回调这些回调只触发一次且按注册顺序执行。3.2 解析 Promise使用resolve($value)方法兑现 Promise。只要传入的值不是GuzzleHttp\Promise\RejectedPromise就会触发所有 onFulfilled 回调若用 rejected promise 作为值解析则该 Promise 会被拒绝并触发$onRejected回调use GuzzleHttp\Promise\Promise; $promise new Promise(); $promise -then(function ($value) { // Return a value and dont break the chain return Hello, . $value; }) // 此 then 在第一个 then 之后执行并接收到第一个 then 的返回值 -then(function ($value) { echo $value; }); // 解析 Promise 触发 $onFulfilled 回调输出 Hello, reader. $promise-resolve(reader.);3.3 Promise 转发Promise ForwardingPromise 可以一个接一个地链式串联。链上的每个then都会产生一个新 Promise上一个 Promise 的返回值会被转发给下一个 Promise。若在then回调中返回一个 Promise则链上后续 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; }); // 触发第一个回调并输出 A $promise-resolve(A); // 触发第二个回调并输出 B $nextPromise-resolve(B);3.4 Promise 拒绝Rejection当 Promise 被拒绝时$onRejected回调会收到拒绝原因use GuzzleHttp\Promise\Promise; $promise new Promise(); $promise-then(null, function ($reason) { echo $reason; }); $promise-reject(Error!); // 输出 Error!3.5 拒绝转发Rejection Forwarding如果$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将拒绝沿链转发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回调既未抛出异常也未返回 rejected promise则下游的$onFulfilled回调会使用该$onRejected回调的返回值use GuzzleHttp\Promise\Promise; $promise new Promise(); $promise -then(null, function ($reason) { return Its ok; }) -then(function ($value) { assert($value Its ok); }); $promise-reject(Error!);四、同步等待Synchronous Wait可以使用wait方法同步强制Promise 完成。创建 Promise 时可以传入一个 wait 函数用于同步驱动 Promise 完成当该函数被调用时它需要向 Promise 交付一个值或拒绝该 Promise。若 wait 函数未交付任何值则抛出异常。构造器中的 wait 函数会在调用 Promise 的wait方法时被触发$promise new Promise(function () use ($promise) { $promise-resolve(foo); }); // 调用 wait 返回 Promise 的值 echo $promise-wait(); // outputs foo若 wait 函数在执行过程中抛出异常则 Promise 以该异常被拒绝并且异常被重新抛出$promise new Promise(function () use ($promise) { throw new Exception(foo); }); $promise-wait(); // throws the exception.对已兑现的 Promise 调用wait不会触发 wait 函数而是直接返回先前解析的值$promise new Promise(function () { die(this is not called!); }); $promise-resolve(foo); echo $promise-wait(); // outputs foo对已拒绝的 Promise 调用wait会抛出异常若拒绝原因是\Exception实例则直接抛出该原因否则抛出GuzzleHttp\Promise\RejectionException可通过其getReason方法取得拒绝原因$promise new Promise(); $promise-reject(foo); $promise-wait();PHP Fatal error: Uncaught exception GuzzleHttp\Promise\RejectionException with message The promise was rejected with value: foo4.1 解包UnwrappingPromise同步等待 Promise 时实际上是在把 Promise 的状态并入当前执行状态兑现则返回值、拒绝则抛异常这称为解包unwrapping。wait默认会解包 Promise 状态。若希望强制 Promise 解析但不解包其状态可向wait的第一个参数传入false$promise new Promise(); $promise-reject(foo); // 不会抛出异常仅确保 Promise 已被解析 $promise-wait(false);解包时若解析值本身仍是 Promise则会持续等待直到最终解包值不再是 Promise。也就是说如果你用 Promise B 解析 Promise A再解包 Await返回的将是交付给 B 的值。注意不解包时不返回任何值。五、取消Cancellation可以通过cancel()方法取消一个尚未兑现的 Promise。创建 Promise 时可以传入可选的 cancel 函数它被调用时会取消该 Promise 解析结果的计算动作use GuzzleHttp\Promise\Promise; $promise new Promise( function () use ($promise) { $promise-resolve(waited); }, function () { // 做取消 Promise 计算的事情例如关闭 socket、取消数据库查询等 } ); assert(waited $promise-wait());从 src/Promise.php 的实现可以看到cancel()会清空 waitFn 与 waitList调用用户提供的 cancelFn若取消函数抛出异常则用该异常拒绝 Promise若取消后 Promise 仍处于 pending 状态则以CancellationException消息 Promise has been cancelled拒绝它。链上后续等待该 Promise 解析的 Promise 也会一并被取消子 Promise 构造时默认把父 Promise 的cancel作为自己的 cancelFn。六、API 详解6.1 Promise 类创建 Promise 对象时可传入可选的$waitFn与$cancelFn。$waitFn是无参函数负责解析 Promise$cancelFn是无参函数在调用 Promise 的cancel()时被触发负责取消 Promise 的计算。Promise 提供以下方法接口定义见 src/PromiseInterface.phpthen(callable $onFulfilled, callable $onRejected) : PromiseInterface向 Promise 追加兑现与拒绝处理器并返回一个新 Promise该 Promise 以被调用处理器的返回值为解析值。otherwise(callable $onRejected) : PromiseInterface追加拒绝处理器返回的新 Promise 以回调返回值解析若原 Promise 被兑现则以原始兑现值解析。wait($unwrap true) : mixed同步等待 Promise 完成。$unwrap控制兑现的 Promise 是否返回值、拒绝的 Promise 是否抛异常默认true。cancel()尽可能取消 Promise。被取消的 Promise 及其最近的尚未解析的祖先 Promise 都会被取消任何等待该 Promise 解析的 Promise 也会被取消。getState() : string返回 Promise 状态取值为pending、fulfilled或rejected对应接口中的PENDING、FULFILLED、REJECTED常量。resolve($value)以给定$value兑现 Promise。reject($reason)以给定$reason拒绝 Promise。6.2 FulfilledPromise用于表示已经兑现的 Promise。回调会被立即触发use GuzzleHttp\Promise\FulfilledPromise; $promise new FulfilledPromise(value); // Fulfilled callbacks are immediately invoked. $promise-then(function ($value) { echo $value; });6.3 RejectedPromise用于表示已经拒绝的 Promise。拒绝回调同样会被立即触发use GuzzleHttp\Promise\RejectedPromise; $promise new RejectedPromise(Error); // Rejected callbacks are immediately invoked. $promise-then(null, function ($reason) { echo $reason; });6.4 静态工具类README 迁移表涉及的工具类均在src/下可直接查阅Utils.phpqueue、task、inspect、inspectAll、unwrap、all、some、any、settle、Create.phppromiseFor、rejectionFor、exceptionFor、iterFor、Is.phppending、settled、fulfilled、rejected、Each.phpof、ofLimit、ofLimitAll。七、Promise 互操作Interoperability本库可与任何实现了then方法的外部 Promiseforeign promise协同工作例如 React Promise。当在then回调中返回外部 Promise 时Promise 的解析会递归进行// 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; });需要注意转发外部 Promise 时无法再使用 wait 与 cancel 链。若想对外部 Promise 使用 wait 与 cancel 功能需要先用 Guzzle Promise 将其包装。这正是 Create.php 中promiseFor所做的工作对非PromiseInterface但存在then方法的对象创建一个 Guzzle Promise 并通过$value-then([$promise, resolve], [$promise, reject])桥接其解析。7.1 事件循环集成Event Loop Integration为了保持栈深度恒定Guzzle Promise 通过一个**任务队列task queue**异步解析。同步等待 Promise 时任务队列会被自动运行以确保阻塞中的 Promise 及其转发的 Promise 都被解析。若在事件循环中异步使用 Promise则需要在循环的每个 tick 运行任务队列否则 Promise 不会被解析。使用全局任务队列实例的run()方法运行队列// Get the global task queue $queue GuzzleHttp\Promise\Utils::queue(); $queue-run();例如可用定时器将 Guzzle Promise 与 React 事件循环结合$loop React\EventLoop\Factory::create(); $loop-addPeriodicTimer(0, [$queue, run]);从 TaskQueue.php 的实现可见任务队列是一个 FIFO 队列add()入队、run()循环出队执行且默认在进程退出非E_ERROR场景时自动排空队列register_shutdown_function也可通过disableShutdown()关闭这一行为此时必须由事件循环或手动run()驱动。八、实现原理Implementation Notes8.1 Promise 解析与链式调用是迭代处理的通过把 pending 的处理器从某个 Promise 转移给另一个 PromisePromise 以迭代方式解析从而支持无限then链。README 给出了 1000 级链的验证示例?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)其底层实现在 src/Promise.php 的settle()中Promise 以非 Promise 值兑现或拒绝时Promise 会接管每个子 Promise 的处理器通过任务队列逐个分发Utils::queue()-add(...)不使用递归Promise 被另一个 Promise解析时原 Promise 会把全部 pending 处理器合并转移给新 Promise$value-handlers array_merge(...)新 Promise 最终被解析时这些处理器会收到转发而来的值。8.2 Promise 即 Deferred部分 Promise 库使用 Deferred 对象表示计算、用 Promise 对象表示计算结果交付从而隔离计算与交付——消费者无法篡改最终交付的值。但本库为了在不公开修改处理器的情况下实现迭代式解析需要某个 Promise 伸入另一个 Promise 的内部状态来转移处理器所有权。因此这里一个 Promise 同时也是 Deferred同类 Promise 可以访问彼此的私有属性完成所有权转移。代价是消费者可以修改 deferred 的解析或拒绝但换来的是栈深度恒定$promise new Promise(); $promise-then(function ($value) { echo $value; }); // The promise is the deferred value, so you can deliver a value to it. $promise-resolve(foo); // prints foo另外从 src/Promise.php 可以看到wait的完整语义等待 pending 的 Promise 时会优先调用 waitFn否则遍历 waitList转发链上的兄弟 Promise逐一waitIfPending随后运行任务队列若 wait 后仍处于 pending则拒绝该 PromiseInvoking the wait callback did not resolve the promise。这也解释了 README 中没有内部 wait 函数的 Promise 无法被 wait的约束。8.3 协程C# 风格 async/awaitsrc/Coroutine.php 用 PHP Generator 实现了协程 Promisegenerator 每次yield出一个 Promise当该 Promise 结算后控制权交回 generator 继续执行最终 Promise 以最后一个 yield 的值兑现。示例如下use GuzzleHttp\Promise; function createPromise($value) { return new Promise\FulfilledPromise($value); } $promise Promise\Coroutine::of(function () { $value (yield createPromise(a)); try { $value (yield createPromise($value . b)); } catch (\Exception $e) { // The promise was rejected. } yield $value . c; }); // Outputs abc $promise-then(function ($v) { echo $v; });其内部通过nextCoroutine()把 yield 出的值交给Create::promiseFor()并注册成功/失败处理器成功时用generator-send($value)继续推进失败时用generator-throw(...)让异常在 generator 内可被捕获从而实现顺序化异步代码的编写方式。九、从函数式 API 升级到静态 API静态 API 自 1.4.0 起引入用于缓解全局函数与本地包之间同名函数冲突的问题函数式 API 将在 2.0.0 移除。以下为官方迁移对照表来源README 的 Upgrading from Function API 一节原函数替代方法queueUtils::queuetaskUtils::taskpromise_forCreate::promiseForrejection_forCreate::rejectionForexception_forCreate::exceptionForiter_forCreate::iterForinspectUtils::inspectinspect_allUtils::inspectAllunwrapUtils::unwrapallUtils::allsomeUtils::someanyUtils::anysettleUtils::settleeachEach::ofeach_limitEach::ofLimiteach_limit_allEach::ofLimitAll!is_fulfilledIs::pendingis_fulfilledIs::fulfilledis_rejectedIs::rejectedis_settledIs::settledcoroutineCoroutine::of十、安全与许可证安全若在本包中发现安全漏洞应通过 securitytidelift.com 私下报告并在修复方案公布前避免公开披露安全问题。许可证Guzzle 基于 MIT 许可证发布许可证全文见 server/vendor/guzzlehttp/promises/LICENSE。结语Guzzle Promises 以迭代解析 任务队列 Promise 即 Deferred三个关键设计在纯 PHP 环境里实现了 Promises/A 兼容且栈深度恒定的 Promise 引擎。无论你是在 ShowDoc 的依赖树中排查 Guzzle 客户端的异步行为还是在自己的 PHP 项目中直接使用它处理并发与顺序异步流程理解then/wait/cancel的完整语义与 src/ 下的实现细节都能帮助你写出更可靠、更可维护的异步 PHP 代码。【免费下载链接】showdocShowDoc is a tool greatly applicable for an IT team to share documents online一个非常适合IT团队的在线API文档、技术文档工具项目地址: https://gitcode.com/gh_mirrors/sh/showdoc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表