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/example/basic-exception.hack:7
Stack trace:
#0 /home/example/basic-exception.hack(15): HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\BasicException\exception_thrower()
#1 /home/example/basic-exception.hack(22): HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\BasicException\basic_exception()
#2 (): HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\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/example/multiple-awaitable-exception.hack:9
Stack trace:
#0 /home/example/multiple-awaitable-exception.hack(17): HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\MultipleAwaitableException\exception_thrower()
#1 /home/example/multiple-awaitable-exception.hack(28): HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\MultipleAwaitableException\multiple_waithandle_exception()
#2 (): HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\MultipleAwaitableException\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:
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) (1) {
["exception":"HH\Asio\WrappedException":private]=>
object(Exception) (8) {
["message":protected]=>
string(0) ""
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(145) "/home/example/wrapping-exceptions.hack"
["line":protected]=>
int(9)
["trace":"Exception":private]=>
varray(3) {
darray(3) {
["file"]=>
string(145) "/home/example/wrapping-exceptions.hack"
["line"]=>
int(18)
["function"]=>
string(105) "HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\WrappingExceptions\exception_thrower"
}
darray(3) {
["file"]=>
string(145) "/home/example/wrapping-exceptions.hack"
["line"]=>
int(31)
["function"]=>
string(107) "HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\WrappingExceptions\wrapping_exceptions"
}
darray(1) {
["function"]=>
string(92) "HHVM\UserDocumentation\Guides\Hack\AsynchronousOperations\Exceptions\WrappingExceptions\main"
}
}
["previous":"Exception":private]=>
NULL
["userMetadata":protected]=>
NULL
}
}
object(HH\Asio\WrappedResult) (1) {
["result":"HH\Asio\WrappedResult":private]=>
int(2)
}
}