What is Async-Transparency?

The most interesting aspect of hyperscript, technically, is that it is async transparent. This means that asynchronous and synchronous code can be mixed together freely and the hyperscript runtime, rather than you, the developer, figures everything out.

That can be a little abstract and so I wanted to take an example of some asynchronous code in javascript and show you what the equivalent hyperscript would be.

To motivate this discussion, we are going to look at the code from How To Master Async/Await With This Real World Example, an excellent practical introduction to the async and await keywords built into javascript.

The author uses a few web APIs to design a small currency converter application in javascript, using axios and the async and await keywords.

The author creates three asynchronous functions:


const getExchangeRate = async (fromCurrency, toCurrency) => {
  try {
    const response = await axios.get('http://data.fixer.io/api/latest?access_key=f68b13604ac8e570a00f7d8fe7f25e1b&format=1');    
    const rate = response.data.rates;
    const euro = 1 / rate[fromCurrency];
    const exchangeRate = euro * rate[toCurrency];    
    return exchangeRate;
  } catch (error) {
    throw new Error(`Unable to get currency ${fromCurrency} and  ${toCurrency}`);
  }
};

const getCountries = async (currencyCode) => {
  try {
    const response = await axios.get(`https://restcountries.eu/rest/v2/currency/${currencyCode}`);
    return response.data.map(country => country.name);
  } catch (error) {
    throw new Error(`Unable to get countries that use ${currencyCode}`);
  }
};

const convert = async (fromCurrency, toCurrency, amount) => {
  const exchangeRate = await getExchangeRate(fromCurrency, toCurrency);
  const countries = await getCountries(toCurrency);
  const convertedAmount = (amount * exchangeRate).toFixed(2);
  return `${amount} ${fromCurrency} is worth ${convertedAmount} ${toCurrency}. You can spend these in the following countries: ${countries}`;
}

The author then uses the convert function like so:

convert('USD', 'HRK', 20)
  .then((message) => {
    console.log(message);
  }).catch((error) => {
    console.log(error.message);
  });

This uses the then callback API of Promises.

All in all, a great little example of how to do asynchronous programming in javascript.

Converting To Hyperscript

So, what does this code look like in hyperscript? Let's port it over!

We'll start with getExchangeRate

def getExchangeRate(fromCurrency, toCurrency)
    fetch http://data.fixer.io/api/latest?access_key=f68b13604ac8e570a00f7d8fe7f25e1b&format=1 as json
    set rates to the rates of the result's data
    set euro to 1 / rates[fromCurrency]
    get euro * rates[toCurrency]
    return it
  catch error
    throw `Unable to get currency ${fromCurrency} and  ${toCurrency}`

So, the first things we do is switch from axios to the fetch command which will pull down the given data for us. We use a "naked string" without quotes here, because the fetch command supports that.

Note that we do not need to say await, rather the hyperscript runtime takes care of that for us.

Finally, we have an as json at the end, to indicate that we want the result parsed as JSON.

The next line is a little clever, we set a variable, rates to the result.data.rates value, but we use hyperscript's of expression, as well as its possessive expression to make the line read more cleanly. We also take advantage of the fact that you can use the definite article, the, before an expression, to help readability.

The next line we set a variable to the inverse of the euro rate for the given currency.

The next line we get the rate multiplied by the euro rate for the target currency.

Finally, we return that value we just calculated. (I like returning a simple symbol like this for debugging, we could have returned the previous line.) We refer to the implicit it symbol, which is an alias for result, which is the standard place where hyperscript stores "the last computed value". We used result above after the fetch.

In hyperscript, a function can have one and only one exception block (this is experimental, and may change) and it will work regardless if the body of the function is synchronous or asynchronous.

OK, so the code looks similar in some ways to the javascript above, but obviously hyperscript has its own flavor.

getCountries is very similar, except we use a string template as our argument to the fetch command:

def getCountries(currencyCode)
    fetch `https://restcountries.eu/rest/v2/currency/${currencyCode}` as json
    get result.map(\ country -> country.name)
    return it
  catch (error)
    throw `Unable to get countries that use ${currencyCode}`

convert is just a bit of glue code to produce a string template and is pretty similar to the javascript, except that there are no awaits

def convert(fromCurrency, toCurrency, amount) 
    set rate to exchangeRate(fromCurrency, toCurrency)
    set countries to countries(toCurrency)
    get (amount * rate).toFixed(2)
    return `${amount} ${fromCurrency} is worth ${result} ${toCurrency} in the following countries: ${countries}`

Note that in the returned string literal, we are referring to the previous value calculated with the result symbol. Again, you may use either it or result depending on which reads more clearly.

The Punchline

So, the hyperscript is maybe a little cleaner, but we haven't really seen the punchline yet, which is the usage of this functionality.

Recall that in the javascript version, you would write code like this to use it:

<button onclick="convert('USD', 'EUR', 10)
                   .then((message) => {
                     document.getElementById('output').innerText = message;
                   })">
  Convert $10 To Euros
</button>
<p id="output"></p>

Here is the equivalent hyperscript:

<button _="put convert('USD', 'EUR', 10) into #output">
  Convert $10 To Euros
</button>
<p id="output"></p>

Because the hyperscript runtime both resolves and creates any promises needed by functions and event handlers under the covers, you are able to write and use the methods the methods in a straight forward, linear fashion you are used to, without annotations (which can cascade through your code base) or using ugly callbacks.

No more (explicit) promises!

Conclusion

I hope that this example gives you a sense of what the async-transparent runtime of hyperscript can do. Hyperscript is designed to simplify front end scripting, increasing the expressiveness to the point that many common patterns can be written inline in HTML, and refocus front end scripting on event handling.

If this sort of thing is interesting to you, you might want to read up on event driven control flow, a novel control flow mechanism enabled by hyperscript's runtime.

Cheers!