The case for async/await-based JavaScript animations
Tags: javascript, animation
Published: 2020-08-18 22:19:37

[`async`/`await` is one of my favorite features](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) of modern JavaScript. While it's just syntactic sugar around [`Promise`s](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), I've found that it enables much more readable and declarative asynchronous code. Recently I've started to experiment with `async`/`await`-based animations, and I've found it to be an effective and standards-based pattern.

## The problem

There is no shortage of great JavaScript animation libraries available. For most use cases, [GreenSock](https://greensock.com) is the gold standard and the library you should default to (and I am saying this as an author of a "competing" library). GreenSock, like most animation libraries such as [Tween.js](https://createjs.com/tweenjs), [anime.js](https://animejs.com), or [mo.js](https://mojs.github.io), has a robust and comprehensive animation-oriented API. This API works well, but like any domain-specific solution, it's an additional layer of programming semantics on top of the language itself. It raises the barrier of entry for newer programmers, and you can't assume that one bespoke API will integrate gracefully with another. What if we could simplify our animation scripting to be more standards-based to avoid these issues?

## The solution: Enter `async`/`await`

`async`/`await` enables us to write asynchronous code as though it was synchronous, thereby letting us avoid unnecessarily nested callbacks and letting code execute more linearly.

**Bias warning**: For the examples in this post I am going to use [Shifty](https://jeremyckahn.github.io/shifty/doc/), an animation library I am the developer of. It is by no means the only library you could use to build `Promise`-based animations, but it does provide it as a first-class feature whereas it's a bit more of an [opt-in feature](https://greensock.com/docs/v3/GSAP/Timeline/then()) for GreenSock and other animation libraries. Use the tool that's right for you!

Here's an animation that uses `Promise`s directly:

```js
import { tween } from 'shifty'

const element = document.querySelector('#tweenable')

tween({
  render: ({ x }) => {
    element.style.transform = `translateX(${x}px)`
  },
  easing: 'easeInOutQuad',
  duration: 500,
  from: { x: 0 },
  to: { x: 200 },
}).then(({ tweenable }) =>
  tweenable.tween({
    to: { x: 0 },
  })
)
```

This is straightforward enough, but it could be simpler. Here's the same animation, but with `async`/`await`:

```js
import { tween } from 'shifty'

const element = document.querySelector('#tweenable')

;(async () => {
  const { tweenable } = await tween({
    render: ({ x }) => {
      element.style.transform = `translateX(${x}px)`
    },
    easing: 'easeInOutQuad',
    duration: 500,
    from: { x: 0 },
    to: { x: 200 },
  })

  tweenable.tween({
    to: { x: 0 },
  })
})()
```

For an example this basic, the difference isn't significant. However, we can see that the `async`/`await` version is free from the `.then()` chaining, which keeps things a bit terser but also allows for a flatter overall code structure (at least once it's inside the `async` [IIFE wrapper](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)).

Because the code is visually synchronous, it becomes easier to mix side effects into the "beats" of the animation:

{% codepen https://codepen.io/jeremyckahn/pen/poyEvog %}

It gets more interesting when we look at using standard JavaScript loops with our animations. It's still weird to me that you can use a `for` or a `while` loop with asynchronous code and not have it block the thread, but `async`/`await` allows us to do it! Here's a metronome that uses a standard `while` loop that repeats infinitely, but doesn't block the thread:

{% codepen https://codepen.io/jeremyckahn/pen/YzqGPqJ %}

Did you notice the `while (true)` in there? In a non-`async` function, this would result in an infinite loop and crash the page. But here, it does exactly what we want!

This pattern enables straightforward animation scripting with minimal semantic overhead from third-party library code. `await` is a fundamentally declarative programming construct, and it helps to wrangle the complexity of necessarily asynchronous and time-based animation programming. I hope that more animation libraries provide first-class `Promise` support to enable more developers to easily write `async`/`await` animations!

## Addendum: Handling interruptions with `try`/`catch`

After initially publishing this post, I iterated towards another powerful pattern that I wanted to share: Graceful animation interruption handling with `try`/`catch` blocks.

Imagine you have an animation running that is tied to a particular state of your app, but then that state changes and the animation either needs to respond to the change or cancel completely. With `async`/`await`-based animations, this becomes easy to do in a way that leverages the fundamentals of language.

{% codepen https://codepen.io/jeremyckahn/pen/abNmGwV %}

In this example, the ball pulsates indefinitely. In the `async` IIFE, notice that the `tween`s are wrapped in a `try` which is wrapped in a `while (true)` to cause the animation to repeat. As soon as you click anywhere in the demo, the animation is `reject`ed, thus causing the `await`ed animation's `Promise` to be treated as a caught exception which diverts the control flow into the `catch` block. Here the `catch` block `await`s `reposition`, another `async` function that leverages a similar pattern to move the ball to where you clicked. Once `reposition` `break`s and exits its `while` loop, the `async` IIFE proceeds to repeat.

This demo isn't terribly sophisticated, but it shows how `async`/`await`-based animations can enable rich interactivity with just a bit of plain vanilla JavaScript!