Web Development With HHVM and Hack 20: Async

Mike Abelar
4 min readMay 3, 2020

--

In the last tutorial, we covered the pipe operator: https://medium.com/@mikeabelar/web-development-with-hhvm-and-hack-19-pipe-operator-cf6aa739cd8a. In this tutorial, we will cover async,

Why use Async?

Async is used when the code needs to run some function that will take time and is not dependant on the CPU. An example of this is if the code needs to query a database. Querying a database is an I/O intensive function as the code needs to ask the database for results and that involves going through the database harddrive. This is clearly unrelated to the CPU (where most code is executed) so it does not make sense for us to have to wait for that database call. Why don’t we do other things in the meantime? A more technical overview of the process is located at on the docs: https://docs.hhvm.com/hack/asynchronous-operations/some-basics

An Example

Illustrating an example above with an actual database is too complicated. We can demonstrate async in a more simple way. Take a look at the code below:

// we will pretend this function
// takes a while to return for demo purposes
// notice the word async before function
// notice that the return type is wrapped in Awaitable<>
async function get_number_async(): Awaitable<int> {
return 5;
}
// any function that uses await must be declared async
// unless you use \HH\Asio\join
// and because main must be async, then we need to wrap
// the return type in Awaitable
<<__EntryPoint>>
async function main_async(): Awaitable<noreturn> {
// here is how to call an async function
$result = await get_number_async();
print("Result: ".$result."\n");
exit(0);
}

When run, we get:

Result: 5

Looking at the code, we see the get_number_async function. All it does is return 5. However, we have two function declaration changes. First, before the function keyword, we use the async keyword. This signifies that the function is async. Then, whenever we have an async function, we must wrap the return type in Awaitable<> .

The main function, now named main_async has the async keyword because it uses the await keyword (And therefore, the return type must be wrapped in Awaitable). The await keyword allows us to wait for the output of get_number_async . This means that we will not execute any code after the get_number_async function call until it returns a value. In this case, we know that the function will instantly return a value. However, if we query a database, we don’t know when the call will finish. Now, if you want to run other code in the meantime, you would need to drop the await keyword. However, you need to be careful with this as you have no guarantee when the function will finish. await guarantees that the function will be done after the line with the await keyword.

Now you may be asking: what’s the point of async functions if we just have to call await anyway? Obviously, you can forgo await and have more complicated logic to detect when the async function call is done. However, one neat application of async functions is that you can run them concurrently as long as they are independent. Imagine in the code above, I had three different get number functions that I all wanted to execute and print their results. Here is how I would do it:

// we will pretend this function
// takes a while to return for demo purposes
// notice the word async before function
// notice that the return type is wrapped in Awaitable<>
async function get_number_async_one(): Awaitable<int> {
return 5;
}
async function get_number_async_two(): Awaitable<int> {
return 6;
}
async function get_number_async_three(): Awaitable<int> {
return 7;
}
// any function that uses await must be declared async
// unless you use \HH\Asio\join
// and because main must be async, then we need to wrap
// the return type in Awaitable
<<__EntryPoint>>
async function main_async(): Awaitable<noreturn> {
// here is how to call an async function
$result_one = await get_number_async_one();
$result_two = await get_number_async_two();
$result_three = await get_number_async_three();
print($result_one."\n");
print($result_two."\n");
print($result_three."\n");
exit(0);
}

Which prints:

5
6
7

However, there is a clear optimization to be made. The three async functions returning numbers are independent and can be run at the same time. There is no need to run one after I have awaited for another. I can run them all at the same time:

// we will pretend this function
// takes a while to return for demo purposes
// notice the word async before function
// notice that the return type is wrapped in Awaitable<>
async function get_number_async_one(): Awaitable<int> {
return 5;
}
async function get_number_async_two(): Awaitable<int> {
return 6;
}
async function get_number_async_three(): Awaitable<int> {
return 7;
}
// any function that uses await must be declared async
// unless you use \HH\Asio\join
// and because main must be async, then we need to wrap
// the return type in Awaitable
<<__EntryPoint>>
async function main_async(): Awaitable<noreturn> {
// here is how to call an async function
// wrap in concurrent - so all three execute at the same time
concurrent {
$result_one = await get_number_async_one();
$result_two = await get_number_async_two();
$result_three = await get_number_async_three();
}
print($result_one."\n");
print($result_two."\n");
print($result_three."\n");
exit(0);
}

As we can see, we wrapped the await calls and the same output occurs. You may even notice it is slightly faster!

--

--