124
6
await
HH\Asio\v(
array
(fetch_from_web(), fetch_from_db(1234)));
return
$web . $db;
}
我们将在本章的余下篇幅里面对这里所发生的事情详细阐述,但是现在你对异步代码有
较高层次的认识即可。
6.2
异步细节
在我们正式开始之前,如果你将要频繁使用异步,我们极力推荐你安装
asio-utilities
,这
是一个异步的助手函数库。我们将在接下来的学习中对这个库的内容进行探讨。当然,
没有这个库,你也可以使用异步,但是它可以使得代码更加简洁。
下载和安装这个类库的推荐途径是使用
Composer
https://getcomposer.org/
),这是一
个对
PHP
Hack
进行包管理的工具。在你的
composer.json
文件中添加如下内容即可:
"require": {
"hhvm/asio-utilities": "
~
1.0"
}
6.2.1
等待句柄
等待句柄(
wait handle
)的概念是异步代码工作的核心内容。等待句柄是一个对象,它
代表了一个可能的异步操作,而这项操作可能完成了,也可能没有完成。如果它完成了,
你可以从等待句柄中获得一个结果。如果没有,你可以等待这个句柄。
等待句柄是用通用的接口
Awaitable
表示的。这里有很多实现了这个接口的类,但这些
都是实现的细节,你并不应该依赖它们。
这里有两种非常重要的等待句柄:
一种代表了异步函数。如果想获得其中一个,只需简单地调用一个异步函数:
async function
f(): Awaitable<int> {
// ...
}
async function
main(): Awaitable<void> {
$wait_handle = f();
//
$wait_handle
是个等待句柄;它的值类型为
Awaitable<int>
$result =
await
$wait_handle;
//
$result
是整型。等待来自
Awaitable
“展开”的结果
}
异步
125
另外一种代表了多个其他的等待句柄。为了得到其中一个句柄,可以使用异步助手
函数
1
HH\Asio\v()
或者
HH\Asio\m()
。前者
用于如下情形:当你已经有一个等待
句柄的索引列表时,例如一个
Vector
,或者一个有着连续整型键的数组。后者用于
如下情形:当你拥有一个等待句柄的对照关系表的时候,例如一个
Map
或者一个拥
有字符串键的数组。具体可以查看下面的例子:
async function
triple(float $number): Awaitable<float> {
return
$number * 3.0;
}
async function
triple_v(): Awaitable<void> {
$handles =
array
(
triple(3.0),
triple(4.0),
);
$result =
await
HH\Asio\v($handles);
var_dump($result[0]);
//
输出
: float(9)
var_dump($result[1]);
//
输出
: float(12)
}
async function
triple_m(): Awaitable<void> {
$handles =
array
(
'three' => triple(3.0),
'four' => triple(4.0),
);
$result =
await
HH\Asio\m($handles);
var_dump($result['three']);
//
输出:
float(9)
var_dump($result['four']);
//
输出
: float(12)
}
HH\Asio\v()
把一个
Vector
或者
Awaitable
类型
的数组转换为一个
Awaitable
类型
Vector
。同样,
HH\Asio\m()
会把一个
Map
或者
Awaitable
型的数组转换为一个
Awaitable
类型的
Map
对于一个非异步函数,如果想获取一个等待句柄之外的结果,在
asio-utilities
中有
个叫做
HH\Asio\join()
函数可以使用
2
。它只有一个
Awaitable
类型的实参。这个
函数会等待这个
Awaitable
执行完毕,然后返回它的结果:
1
这些函数是
HHVM
的内置函数。它们并不是
asio-utilities
的一部分。你可以在不安装
这个类库的前提下,使用这些内置函数。
2
HH\Asio\join()
asio-utilities
的一部分,但是将来它会成为
HHVM
的内置函数。
通常来说,在新特性合并到
HHVM
自身之前,
asio-utilities
HHVM
团队测试新异步
API
的地方。
126
6
async function
f(): Awaitable<mixed> {
// ...
}
function
main(): void {
$result = HH\Asio\join(f());
}
在一个异步函数的内部,你不应该使用
HH\Asio\join()
函数。如果你这样做了,这个
Awaitable
和它的依赖项将会同步完成,你当前等待的
Awaitable
将无法得到运行的机会。
如果在一个异步函数内部,你将会得到一个等待句柄,它的返回结果就是你想要的值,
请耐心等待。
6.2.2
异步和可调用类型
1.4
节中,我们已经看到了
Hack
有对可调用值类型进行标注的相关语法。在这个例子
中,你必须对函数
f()
传递另外一个函数作为参数,这个作为参数的函数以一个整数作
为参数,并且返回一个字符串:
function
f((
function
(int): string) $callback): void {
// ...
}
function
main(): void {
$good =
function
(int $x): string {
return
(string)$x; };
f($good); //
OK
$bad =
function
(
array
$x): int {
return
count($x); };
f($bad); //
错误
}
现在你可能会问:对于异步函数,你如何实现这个功能呢?在本例中,如果你必须传递
一个异步函数作为回调,
f()
该如何指明呢?
这里有充分的理由告诉你,你不能这样做。函数的异步性是这个函数的一个实现细节。
在一个函数上使用关键词
async
,会做如下两件事情:
它允许这个函数在它的函数体内使用关键词
await
,这是一个实现细节,而不是关
系到函数外部的任何代码的东西。
它强制函数的返回类型为
Awaitable
。返回类型肯定和函数外部代码是关系。但是
这个关系型仅仅局限于这个返回值,而不会关系到函数的异步性。
为了返回上一个例子
f()
以指定这个回调必须返回一个
Awaitable<string>
。这
会允许(但不是要求)一个异步函数作为回调传递进来:
异步
127
function
f((
function
(int): Awaitable<string>) $callback): void {
// ...
}
为了更加清晰地阐述上述原因,考虑另外一个函数的实现细节:它是不是闭包。在这个
例子中,“允许
f()
指定必须传递给它一个异步函数”和“允许
f()
明必须传递给它
一个闭包”,这两种行为是一样愚蠢的。
出于相同的原因,对于抽象方法或者接口中的方法,并不能声明它们为异步的。当然,
可以声明它们为非异步的,还可以把
Awaitable
作为它们的返回类型:
interface
I {
public async function
bad(): Awaitable<void>; //
错误
public function
good(): Awaitable<void>; //
OK
}
abstract class C
{
abstract public async function
bad(): Awaitable<void>; //
错误
abstract public function
good(): Awaitable<void>; //
OK
}
6.2.3 await
并不是一个表达式
虽然
await
的行为从很多角度上看都很像一个表达式,但它并不是通常意义上的表达式。
这里有它可以出现的三个句法位置:
自己作为一整条语句:
async function
f(): Awaitable<void> {
await
other_func();
}
在通常的赋值或者
list
赋值语句中,出现在右边:
async function
f(): Awaitable<void> {
$result =
await
other_func();
list
($one, $two) =
await
yet_another_func();
}
作为一个
return
语句的实参:
async function
f(): Awaitable<mixed> {
return await
other_func();
}
如果你在其他地方使用
await
,这是一个语法错误。因此,你并不能像如下例子一样使
用它:
async function
f(): Awaitable<void> {
var_dump(
await
other_func()); //
语法错误
128
6
}
这种限制可能在将来解除。它目前存在只是因为一些实现问题
3
6.2.4
异步生成器
生成器在
PHP 5.5
中被正式引入。表面上来看,它们看起来和异步函数非常类似。这两
个特性都引入了一种特殊的函数,它们具有中途暂停执行的能力。以这样的方式,它们
可以再次回到离开的地方恢复执行。
然而,这两种特性是正交的像其他函数一样,生成器也可以是异步的。这里有一个例子,
它实现了倒计时的时钟,每一秒
yield
一次(具体参见
6.4.1
节中的
HH\Asio\usleep()
相关内容):
async function
countdown(int $start): AsyncIterator<int> {
for
($i = $start; $i >= 0; --$i) {
await
HH\Asio\usleep(1000000);
//
休眠
1
yield
$i;
}
}
在这里需要特别注意的一点就是返回类型标注:
AsyncIterator<int>
。这意味着可以对
countdown()
函数的
返回值进行迭代,并且你得到的迭代值为整型。
然而,这是一个异步迭代,而不是一个常规迭代。这里有一些新的语法用于对异步迭代
器进行迭代,即
await as
async function
use_countdown(): Awaitable<void> {
$async_gen = countdown();
foreach
($async_gen
await as
$value) {
//
这里
$value
的类型为
int
var_dump($value);
}
}
await as
语法是重复调用
await $async_gen->next()
简写,就像常见的
foreach
法是在一个常见迭代器上面重复调
next()
缩写一样。
如果你希望从一个异步生成器中得到一个键,请使用接口
AsyncKeyedIterator
。它使用
两个类型实参:键的类型和值的类型。如果要对它们中的某一个进行迭代的话,你也可
3
事实上,在
await
可以出现的地方的相关约束下,这里没有什么办法能够使一个异步函数
能在执行一个表达式的中途暂停。如果
await
能够在任意地方出现,我们将会面临的问题
就是如何有效地存储表达式的中间计算状态。这可并不是听起来那么简单的事情。

Get Hack 与HHVM 权威指南 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.