Asynchronous Operations: Exceptions
In general, an async operation has the following pattern:
- Call an
async
function - Get an awaitable back
await
the awaitable to get a result
However, sometimes an async function can throw an exception. The good news is that the same exception object that would be thrown in the
non-async version of the code will be returned when we await
the awaitable.
async function exception_thrower(): Awaitable<void> {
throw new \Exception("Return exception handle");
}
async function basic_exception(): Awaitable<void> {
// the handle does not throw, but result will be an Exception objection.
// Remember, this is the same as:
// $handle = exception_thrower();
// await $handle;
await exception_thrower();
}
<<__EntryPoint>>
function main(): void {
\HH\Asio\join(basic_exception());
}
Fatal error: Uncaught exception 'Exception' with message 'Return exception handle' in /home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/basic-exception.php:6
Stack trace:
#0 /home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/basic-exception.php(14): Hack\UserDocumentation\AsyncOps\Exceptions\Examples\BasicException\exception_thrower()
#1 /home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/basic-exception.php(19): Hack\UserDocumentation\AsyncOps\Exceptions\Examples\BasicException\basic_exception()
#2 (): Hack\UserDocumentation\AsyncOps\Exceptions\Examples\BasicException\main()
#3 {main}
The use of from_async
ignores any successful awaitable results and just throw an exception of one of the
awaitable results, if one of the results was an exception.
async function exception_thrower(): Awaitable<void> {
throw new \Exception("Return exception handle");
}
async function non_exception_thrower(): Awaitable<int> {
return 2;
}
async function multiple_waithandle_exception(): Awaitable<void> {
$handles = vec[exception_thrower(), non_exception_thrower()];
// You will get a fatal error here with the exception thrown
$results = await Vec\from_async($handles);
// This won't happen
\var_dump($results);
}
<<__EntryPoint>>
function main(): void {
\HH\Asio\join(multiple_waithandle_exception());
}
Fatal error: Uncaught exception 'Exception' with message 'Return exception handle' in /home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/multiple-awaitable-exception.php:7
Stack trace:
#0 /home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/multiple-awaitable-exception.php(15): Hack\UserDocumentation\AsyncOps\Exceptions\Examples\MultipleAwaitable\exception_thrower()
#1 /home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/multiple-awaitable-exception.php(24): Hack\UserDocumentation\AsyncOps\Exceptions\Examples\MultipleAwaitable\multiple_waithandle_exception()
#2 (): Hack\UserDocumentation\AsyncOps\Exceptions\Examples\MultipleAwaitable\main()
#3 {main}
To get around this, and get the successful results as well, we can use the utility function
HH\Asio\wrap
. It takes an awaitable and returns the expected result or the exception
if one was thrown. The exception it gives back is of the type ResultOrExceptionWrapper
.
namespace HH\Asio {
interface ResultOrExceptionWrapper<T> {
public function isSucceeded(): bool;
public function isFailed(): bool;
public function getResult(): T;
public function getException(): \Exception;
}
}
Taking the example above and using the wrapping mechanism, this is what the code looks like:
require __DIR__."/../../../../vendor/hh_autoload.php";
async function exception_thrower(): Awaitable<void> {
throw new \Exception();
}
async function non_exception_thrower(): Awaitable<int> {
return 2;
}
async function wrapping_exceptions(): Awaitable<void> {
$handles = vec[
\HH\Asio\wrap(exception_thrower()),
\HH\Asio\wrap(non_exception_thrower()),
];
// Since we wrapped, the results will contain both the exception and the
// integer result
$results = await Vec\from_async($handles);
\var_dump($results);
}
<<__EntryPoint>>
function main(): void {
\HH\Asio\join(wrapping_exceptions());
}
vec(2) {
object(HH\Asio\WrappedException)#5 (1) {
["exception":"HH\Asio\WrappedException":private]=>
object(Exception)#3 (8) {
["message":protected]=>
string(0) ""
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(128) "/home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/wrapping-exceptions.php"
["line":protected]=>
int(9)
["trace":"Exception":private]=>
array(3) {
[0]=>
array(3) {
["file"]=>
string(128) "/home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/wrapping-exceptions.php"
["line"]=>
int(17)
["function"]=>
string(78) "Hack\UserDocumentation\AsyncOps\Exceptions\Examples\Wrapping\exception_thrower"
}
[1]=>
array(3) {
["file"]=>
string(128) "/home/ubuntu/user-documentation-2018-09-07/guides/hack/09-asynchronous-operations/10-exceptions-examples/wrapping-exceptions.php"
["line"]=>
int(27)
["function"]=>
string(80) "Hack\UserDocumentation\AsyncOps\Exceptions\Examples\Wrapping\wrapping_exceptions"
}
[2]=>
array(1) {
["function"]=>
string(65) "Hack\UserDocumentation\AsyncOps\Exceptions\Examples\Wrapping\main"
}
}
["previous":"Exception":private]=>
NULL
["userMetadata":protected]=>
NULL
}
}
object(HH\Asio\WrappedResult)#8 (1) {
["result":"HH\Asio\WrappedResult":private]=>
int(2)
}
}