drnkwati/guzzle-promises

PHP 5.3 兼容的 guzzle/promises 分支。

dev-master / 1.0.x-dev 2018-03-13 15:30 UTC

This package is auto-updated.

Last update: 2024-09-22 11:03:40 UTC


README

Build Status Latest Stable Version License

A PHP 5.3 兼容的 guzzle/promises 分支。

为什么?

遗憾的是,60% 的所有 PHP 服务器仍然运行 PHP 5.4 及以下版本,但 guzzle/psr7 需要 PHP 5.5 或更高版本。这个分支使 guzzle/promises 兼容 PHP 5.3.29 至 7.1。

如何使用此分支

用法与 guzzle/promises 相同

Promises/A+ 实现,可迭代处理承诺链和解析,允许进行“无限”承诺链,同时保持堆栈大小不变。阅读 此博客文章 了解承诺的简介。

功能

  • Promises/A+ 实现。
  • 承诺解析和链式处理是迭代的,允许进行“无限”承诺链。
  • 承诺有一个同步的 wait 方法。
  • 承诺可以被取消。
  • 与任何具有 then 函数的对象兼容。
  • 使用 GuzzleHttp\Promise\coroutine() 进行 C# 风格的异步/等待协程承诺。

快速开始

承诺表示异步操作最终结果。与承诺交互的主要方式是通过其 then 方法,该方法注册回调以接收承诺的最终值或承诺无法履行的原因。

回调

通过提供可选的 $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.';
    }
);

解析 一个承诺意味着你用 履行一个承诺或用 原因 拒绝一个承诺。解析承诺会触发与承诺的 then 方法注册的回调。这些回调仅触发一次,并且按照它们添加的顺序。

解析承诺

使用 resolve($value) 方法履行承诺。用除 GuzzleHttp\Promise\RejectedPromise 之外的任何值解析承诺将触发所有 onFulfilled 回调(用拒绝的承诺解析承诺将拒绝承诺并触发 $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.');

承诺转发

承诺可以一个接一个地串联。链中的每个 then 都是一个新的承诺。承诺的返回值将转发到链中的下一个承诺。在 then 回调中返回承诺将导致链中的后续承诺仅在返回的承诺被履行时才被履行。链中的下一个承诺将用承诺的解析值调用。

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');

承诺拒绝

当承诺被拒绝时,$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 来将拒绝沿着承诺链向下转发。

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,则下游的$onFulfilled回调将使用从$onRejected回调返回的值来调用。

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完成的wait函数。当调用wait函数时,它应该向Promise提供一个值或者拒绝Promise。如果wait函数不提供值,则会抛出异常。当Promise的wait函数被调用时,会调用传递给Promise构造函数的wait函数。

$promise = new Promise(function () use (&$promise) {
    $promise->resolve('foo');
});

// Calling wait will return the value of the promise.
echo $promise->wait(); // outputs "foo"

如果在调用Promise的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致命错误:未捕获的异常 'GuzzleHttp\Promise\RejectionException',消息为'The promise was rejected with value: 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,则wait函数返回的值将是传递给Promise B的值。

注意:当您不解包Promise时,不会返回任何值。

取消

您可以使用Promise的cancel方法取消尚未解决的Promise。在创建Promise时,您可以提供一个可选的cancel函数,当调用该函数时,会取消计算Promise的解决操作。

API

承诺

在创建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,该Promise解决为被调用处理器的返回值。

  • otherwise(callable $onRejected) : PromiseInterface

    向Promise追加一个拒绝处理程序回调,如果Promise被拒绝,并且回调被调用,则返回一个新Promise,该Promise解决为回调的返回值;如果Promise被解决,则返回其原始解决值。

  • wait($unwrap = true) : mixed

    同步等待Promise完成。

    $unwrap控制对于已解决的Promise返回Promise的值,还是如果Promise被拒绝,则抛出异常。默认设置为true

  • cancel()

    尽可能取消承诺。正在取消的承诺及其父级最远的未解决祖先也将被取消。任何等待已取消承诺解决的承诺也将被取消。

  • getState() : string

    返回承诺的状态。可以是 pendingfulfilledrejected 之一。

  • resolve($value)

    使用给定的 $value 满足承诺。

  • reject($reason)

    使用给定的 $reason 拒绝承诺。

已履行承诺

可以创建一个满足的承诺来表示已满足的承诺。

use GuzzleHttp\Promise\FulfilledPromise;

$promise = new FulfilledPromise('value');

// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
    echo $value;
});

拒绝承诺

可以创建一个拒绝的承诺来表示已拒绝的承诺。

use GuzzleHttp\Promise\RejectedPromise;

$promise = new RejectedPromise('Error');

// Rejected callbacks are immediately invoked.
$promise->then(null, function ($reason) {
    echo $reason;
});

承诺互操作性

此库与具有 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->resolve('foo');
// prints "foo"