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];
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.
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.
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!
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!