moebius/promise

PHP 的灵活且兼容的 Promise 实现。

1.0.103 2022-06-24 11:25 UTC

README

一个纯粹的 Promises/A+ Promise 实现,设计上灵活且经过良好测试 - 并促进互操作性。

Promise 接口

Promise 对象具有以下简洁的接口。此接口和实现尽可能紧密地遵循 ECMAScript Promise 规范。这意味着您可以假设您从 JavaScript 中了解的相同语义将适用于这些 Promise。

namespace Moebius;

interface PromiseInterface {

    /**
     * Schedule a callback to run when the promise is fulfilled
     * or rejected.
     *
     * @param callable $onFulfill   Callback which will be invoked if the promise is fulfilled.
     * @param callable $onReject    Callback which will be invoked if the promise is rejected.
     * @param callable $void        Ignored; for compataiblity with other promise implementations.
     * @return PromiseInterface     Returns a new promise which is resolved with the return value of $onFulfill/$onReject
     */
    public function then(callable $onFulfill=null, callable $onReject=null, callable $void=null): PromiseInterface;

    /**
     * Is the promise still pending resolution?
     */
    public function isPending(): bool;

    /**
     * Is the promise fulfilled?
     */
    public function isFulfilled(): bool;

    /**
     * Is the promise rejected?
     */
    public function isRejected(): bool;

}

互操作性

对于具有 Promise 意识的 PHP 库,显式进行对象类型检查以确保兼容性是非常常见的。为此,此库提供了一组类型检查函数。

类型转换

如果您需要确保提供的对象遵循 Promises/A+ 语义,并具有可链的 then(),则应使用 Moebius\Promise::cast() 方法。

use Moebius\Promise;

$promise = Promise::cast($somePromise);

// Now you can safely check if the promise has been resolved

类型检查

use Moebius\Promise;

if (Promise::isPromise($someObject)) {
    // You can treat $someObject as a promise
}

// 类型转换 $promise = Promise::cast($someObject);

转换 Promise

来自其他实现的 Promise 可以转换为 Moebius\Promise。

<?php
$promise = Moebius\Promise::cast($otherPromise);

识别类似 Promise 的对象

由于没有标准的 PHP Promise 接口,因此有一种简单的方法来检查一个对象是否看起来像是一个有效的 Promise 是很有用的。

<?php
if (Moebius\Promise::isPromise($otherPromise)) {
    // promise has a valid `then()` method
}

创建 Promise

通常有两种方式可以创建 Promise;使用解析器函数或不需要解析器函数 - 在这种情况下,Promise 必须通过调用 fulfill()reject() 方法来解决。

使用解析器函数*

<?php
$promise = new Promise(function(callable $fulfill, callable $reject) {
    $fulfill("Hello World");
});
$promise->then(function($value) {
    echo $value."\n";
});

不使用解析器函数

<?php
$promise = new Promise();
$promise->then(function($value) {
    echo $value."\n";
});
$promise->resolve("Hello World");

互操作性说明

该接口侧重于 Promise 的解决;它不关心 Promise 是如何创建的 - 该接口是用于接受 Promise 的函数。

大多数 Promise 实现都有一个庞大的 API,但 Promise 只需要公开一个 then() 函数即可被 90% 的库使用。

还有助于确定一个 Promise 是否已经解决或拒绝的方法也非常有用 - 通常是通过返回一个字符串 "pending"、"resolved"、"rejected" 或 "fulfilled" 的方法实现的。

由于这些方法可以根据底层 Promise 实现记录状态的方式来实现,因此此库实现了 isPending()isResolved()isFulfilled() 方法。

许多 Promise 实现都有类似于 otherwise()finally() 和类似的函数 - 虽然可能很方便,但这会降低互操作性,因为相同的特性可以以多种方式实现,并且具有不同的名称。

一个与各种库广泛兼容的 Promise 实现。

模仿 JavaScript 中的 Promises/A+ 规范。

如果您创建了一个 Moebius\Promise 实例,它可以直接用作以下内容的嵌入式替代品

  • guzzlehttp/promises 通过实现 GuzzleHttp\Promise\PromiseInterface
  • react/promise 通过实现 React\Promise\PromiseInterface
  • 'php-http/promise' 通过实现 Http\Promise\Promise
  • amphp/amp 通过实现 Amp\Promise

Moebius\Promise 对象有两个主要用途:作为标准的 Promise 对象,以及使用 React 术语作为 Deferred 对象。

基本的 Promise 使用方法

这是使用 Promise 最常见的方式

use Moebius\Promise;

function some_future_result() {
    return new Promise(function($fulfill, $reject) {
        /**
         * Either fulfill the promise directly here, by calling
         * the provided $fulfill(VALUE) and $reject(REASON) callbacks
         * immediately, or make sure that one of these are called at
         * a later time.
         */
    });
}

"Deferred" 使用

在 React 和其他一些库中,一种额外的 Promise 类型被称为 "deferred" Promise。Moebius 结合了这两种用途

use Moebius\Promise;

function some_future_result() {
    $result = new Promise();

    /**
     * Make sure that the promise is resolved now, by calling
     * `$result->resolve(VALUE)` or `$result->reject(REASON)`
     * here, or make sure that one of them will be called in
     * the future.
     */

    return $result;
}

自己支持其他 Promise

为了支持其他承诺实现,通常的做法是使用反射来检查then()方法。

Moebius Promise提供了两种方法来支持其他承诺并确保一致的用法

类型转换

use Moebius\Promise;

function accepting_a_promise(object $thenable) {
    /**
     * @throws InvalidArgumentException if the object is not a promise
     */
    $promise = Moebius\Promise::cast($thenable);
}

实现细节

为了同时保持Guzzle和PhpHttp承诺的兼容性,我们进行了一些“诡计”。结果是,PhpHttp承诺是基于Guzzle承诺构建的,实际上是兼容的。Guzzle承诺是PhpHttp承诺的超集,所以如果安装了PhpHttp,它将通过class_alias()替换GuzzleHttp\Promise\PromiseInterface

这些常量现在位于Moebius\Promise\SuperPromiseInterface类中,这样它们在子类中可用,而不会根据PHP产生歧义。

承诺解析

在承诺解析方面,Guzzle非常特别,它会在您实际请求值时才延迟运行解析函数。这对于事件循环实现来说并不理想——因为例如,如果您的任务需要1秒才能运行,您会希望尽快开始运行该任务。当仅使用Guzzle时,这可能不太明显,但当与其他异步任务结合使用时,这会变得令人烦恼。

我们预计这不会成为问题。

Promise::wait()

Guzzle和PhpHttp实现了wait()函数,该函数旨在启动异步作业的运行——几乎就像使用承诺树作为事件循环一样。

基本用法

通常,您将在构建承诺时传递一个解析函数。这是使用承诺最常见的方式。

use Moebius\Promise;

function some_function() {
    return new Promise(function($resolve, $reject) {
        // This function is immediately run when constructing the promise

        $resolve("Some value");         // or $reject(new Exception());
    });
}

延迟用法

在某些情况下,您无法从承诺内部解析承诺。这样,您可以保留承诺的引用,并在返回后解析(或拒绝)它。

use Moebius\Promise;

$promise = new Promise();

// send the promise off to some function
some_function($promise);

// resolve it at any later time
$promise->resolve("Some value");    // or $promise->reject(new Exception())

注意:虽然可以多次调用解析/拒绝方法,但只有第一次调用有效。

API文档

高效处理多个承诺

  • Promise::all(iterable $promises): Promise。如果所有承诺都解析成功,则返回的承诺将以所有承诺的数组解析。如果任何承诺被拒绝,则返回的承诺也会被拒绝。

  • Promise::allSettled(iterable $promises): Promise。一旦所有承诺都有结果,返回的承诺将以承诺的数组解析。

  • Promise::any(iterable $promises): Promise。如果任何承诺被解析,则返回的承诺将以第一个值解析。如果所有承诺都拒绝,则返回的承诺也会被拒绝。

  • Promise::race(iterable $promises): Promise。第一个解析或拒绝的承诺将是返回承诺的结果。

与其他承诺实现互操作

  • Promise::cast($promise): Promise。任何具有类似承诺的“then”函数的对象都将转换为Moebius\Promise实例。

订阅承诺的结果

  • $promise->then(callable $onFulfilled=null, callable $onRejected=null): Promise。如果承诺已经解决(解析或拒绝),则立即调用传递的函数。返回的承诺是一个新承诺,它将根据回调解析或拒绝值。

外部解析承诺

  • $promise->resolve($value).

  • $promise->reject($reason).