ajayvohra2005 / hack-promises
Hack语言中的Promises/A+实现
Requires
- hhvm: ^4.128
- hhvm/hhvm-autoload: ^3.3
Requires (Dev)
- facebook/fbexpect: ^2.8
- hhvm/hacktest: ^2.2
- hhvm/hhast: ^4.123
README
本项目提供Promises/A+在Hack中的实现。
概述
关键特性
- Promise具有同步的
wait
和cancel
方法。 - 与任何实现了
HackPromises\ThenableInterface
的对象一起工作 - 支持Hack
HH\KeyedIterator
和Hack 生成器。
要求
HHVM 4.128及以上。
安装
-
使用Git克隆此仓库
-
安装composer
-
在此仓库的根目录下运行以下命令
composer install
要使用此包,
composer require ajayvohra2005/hack-promises
运行测试
安装后,在仓库根目录下运行以下命令
./vendor/bin/hacktest tests/
许可协议
本项目在MIT许可证(MIT)下提供。有关更多信息,请参阅本项目的LICENSE文件。
教程
Promise表示异步操作的结果及其相关副作用。
解决Promise
解决Promise意味着Promise要么以value被实现,要么以reason被拒绝。解决Promise将触发与Promise的then
方法注册的回调。这些回调只触发一次,并且按照它们被添加的顺序触发。当回调被触发时,它被添加到全局任务队列中。当全局任务队列被run
时,队列上的任务将被移除并按它们被添加到队列的顺序执行。
全局任务队列
全局任务队列可以根据需要或事件循环运行。默认情况下,全局任务队列在程序关闭之前隐式地运行一次。全局任务队列可以像下面这样显式地运行
HackPromises\TaskQueue::globalTaskQueue()->run();
回调
通过调用Promise的then
方法并提供一个可选的$onFulfilled
和$onRejected
函数来注册回调,其中这些函数必须是HackPromises\ThenCallback
类型的。
当你通过调用Promise的then
方法注册回调时,它总是返回一个新的Promise。这可以用来创建Promise链。链中的下一个Promise使用链中前一个Promise的解决值调用。只有当链中的前一个Promise被解决时,链中的Promise才会被解决。
快速示例
以下是一个快速入门示例,说明了迄今为止讨论的概念
use namespace HackPromises;
use type HackPromises\Promise;
<<__EntryPoint>>
function quick_start_example(): void {
require_once(__DIR__.'/../vendor/autoload.hack');
\Facebook\AutoloadMap\initialize();
$promise = new Promise();
$promise
->then((mixed $value): mixed ==> {
// Return a value and don't break the chain
return "Hello, " . ($value as string);
})
// This then is executed after the first then and receives the value
// returned from the first then.
->then((mixed $value): void ==> {
$msg = $value as string;
echo "{$msg}\n";
});
// Resolving the promise triggers the callback, and outputs 'Hello, reader.'.
// The callbacks are executed on the global task queue, prior to program shutdown
$promise->resolve('reader.');
}
Promise拒绝
当Promise被拒绝时,$onRejected
回调将使用拒绝原因调用,如下面的示例所示
use type HackPromises\Promise;
<<__EntryPoint>>
function promise_rejection_example(): void {
require_once(__DIR__.'/../vendor/autoload.hack');
\Facebook\AutoloadMap\initialize();
$promise = new Promise();
$promise
->then(null, (mixed $reason): void ==> {
$msg = $reason as string;
echo "{$msg}\n";
});
// Outputs "Error!"
$promise->reject('Error!');
}
拒绝转发
如果在$onRejected
回调中抛出异常,后续的$onRejected
回调将使用抛出的异常作为原因调用。
use type HackPromises\{Promise, RejectedPromise};
<<__EntryPoint>>
function rejection_forwarding_example(): void {
require_once(__DIR__.'/../vendor/autoload.hack');
\Facebook\AutoloadMap\initialize();
$promise = new Promise();
$promise
->then(null, (mixed $reason): mixed ==> {
return new RejectedPromise($reason);
})
->then(null, (mixed $reason): void ==> {
});
// Outputs nothing
$promise->reject('No Error!');
}
如果$onRejected
回调中没有抛出异常,并且回调没有返回一个拒绝的Promise,那么将使用从$onRejected
回调返回的值调用下游的$onFulfilled
回调,如下面的示例所示
use namespace HackPromises as P;
<<__EntryPoint>>
function ignore_rejection_example(): void
{
require_once(__DIR__.'/../vendor/autoload.hack');
\Facebook\AutoloadMap\initialize();
$promise = new P\Promise();
$promise ->then(null, (mixed $reason): mixed ==> {
return "It's ok";
})->then( (mixed $value): void ==> {
echo $value as string;
});
// Outputs 'It's ok'
$promise->reject('Error!');
}
同步等待
您可以通过在承诺上调用 wait
方法来同步解决承诺。
调用承诺的 wait
方法会调用承诺提供的 wait 函数,并隐式运行全局任务队列。以下是一个展示 wait
方法使用的示例:
use namespace HackPromises;
use type HackPromises\Promise;
use type HackPromises\ResolveCallback;
<<__EntryPoint>>
function promise_wait_function_example(): void {
require_once(__DIR__.'/../vendor/autoload.hack');
\Facebook\AutoloadMap\initialize();
$wait_fn = (ResolveCallback $cb) ==> {
$cb('reader.');
};
$promise = new Promise($wait_fn);
$promise
->then((mixed $value): mixed ==> {
// Return a value and don't break the chain
return "Hello, " . ($value as string);
})
// This then is executed after the first then and receives the value
// returned from the first then.
->then((mixed $value): void ==> {
$msg = $value as string;
echo "{$msg}\n";
});
// Calling wait resolves promise synchronously and outputs 'Hello, reader.'.
$promise->wait();
}
如果在调用承诺的 wait 函数时遇到异常,承诺将被拒绝,并抛出异常。调用已解决的承诺的 wait
方法不会触发 wait 函数。它将直接返回之前解决的价值。
对已拒绝的承诺调用 wait
方法将抛出异常。如果拒绝原因是一个 \Exception
的实例,则抛出 reason。否则,将抛出 HackPromises\RejectionException
,可以通过调用异常的 getReason
方法来获取 reason。
拆包承诺
当您不带参数或使用参数 true
调用 wait
方法时,它会拆包承诺。拆包承诺如果承诺是 fulfilled,则返回承诺的值,如果承诺是 rejected,则抛出异常。您可以通过将 false
传递给承诺的 wait
方法来强制承诺解决但 不 拆包,如下面的示例所示:
$promise = new Promise();
$promise->reject('foo');
// This will not throw an exception. It simply ensures the promise has
// been resolved.
$promise->wait(false);
在拆包承诺时,承诺的解决值将等待,直到拆包的值不再是承诺。这意味着如果您用承诺 B 解决承诺 A 并拆包承诺 A,wait 函数返回的将是解决承诺 B 的结果,如下面的示例所示:
use namespace HackPromises as P;
<<__EntryPoint>>
function unwrapping_example(): void
{
require_once(__DIR__.'/../vendor/autoload.hack');
\Facebook\AutoloadMap\initialize();
$b = new P\Promise();
$a = new P\Promise( (P\ResolveCallback $cb): void ==> { $cb($b);});
$b->resolve('foo');
$result = $a->wait() as string;
// Outputs 'foo'
echo $result;
}
同步取消
您可以使用承诺的 cancel
方法同步取消尚未解决的承诺。
API 类
Promise
对于基本案例,您可以使用 Promise
类创建承诺。
如果您想异步解决承诺并调用已注册的 then
回调,您可以在构造函数中不传递任何参数来创建 Promise
。
如果您想能够同步解决或取消 Promise
,请将 wait 和 cancel 函数传递给构造函数。
等待函数
等待函数必须是 HackPromises\WaitFunction
类型。提供的给 Promise
构造函数的等待函数会在调用 Promise
的 wait
方法时被调用。等待函数必须通过调用作为参数传递给它的 HackPromises\ResolveCallback
函数来解决承诺,或者抛出异常。
取消函数
在创建 Promise
时,您可以选择提供一个类型为 HackPromises\CancelFunction
的可选 cancel 函数,该函数在调用承诺的 cancel
方法时被调用。取消函数可以可选地通过调用作为参数传递给它的 HashPromises\RejectCallback
函数来拒绝承诺。
FulfilledPromise
FulfilledPromise
类对象在构造时解决。
RejectedPromise
RejectedPromise
类对象在构造时拒绝。
IteratorPromise
IteratorPromise
类创建一个迭代器承诺,该迭代器遍历承诺或值的迭代器,并在过程中触发回调函数。使用 Create::iterFor
为 vec<mixed>
或 dict<arraykey, mixed>
创建一个迭代器。
GeneratorPromise
GeneratorPromise
类包装一个产生值或承诺的生成器。
Create
Create
类提供辅助方法。
致谢
本项目受Guzzle Promises启发。然而,由于Hack语言和最佳实践的要求,它在API和实现方面存在显著差异。