Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 8.5Intermediate ~23 min · shot 5 of 6

Promises, Events and getAnimations

In one line

Every Animation has a ready and a finished promise, so motion can be sequenced with await. cancel() rejects the promise, getAnimations() lists every animation including CSS ones, and composite: "add" lets two animations share one property.

0/6 · 0%

Think of it like this

A relay race. Each runner waits for the baton before starting, and the race can be called off. await anim.finished is the baton hand-over. If the race is called off, the waiting runner is told so (an error) and must not stand there forever.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

ready
A promise that resolves when the animation has started playing, one frame after play or animate().
finished
A promise that resolves when the animation reaches its end, and rejects with AbortError if it is cancelled.
AbortError
The DOMException with which a cancelled animation rejects its finished promise.
CSSAnimation / CSSTransition
The Animation subclasses the browser creates for CSS animations and transitions. They appear in getAnimations().
composite
How an animation combines with the value below it: replace (default), add or accumulate.
unhandled rejection
A rejected promise nobody catches. The console prints Uncaught (in promise) and the code after it does not run.

Step by step

01Follow the sequence

Run "Await a sequence". ready resolves with the animation itself. Step 1 and step 2 print one after the other, each when its animation really ends. Then finished promise prints before onfinish event.

02Cancel it

The next line shows the rejection: awaiting a cancelled animation -> AbortError: The user aborted a request. And the line after: reading finished again gives a promise that is still pending after 5 frames.

03List everything

Run "Everything on the page". document.getAnimations() names a CSSTransition opacity, a CSSAnimation turn and your Animation slide. Pause the timeline and look: three tracks, one for each.

04Add instead of replace

Run "Replace or add". The numbers show the left square rotated 90 degrees, the middle one scaled 1.25 with no rotation at all, and the right one both rotated and scaled.

Try it yourself

  1. 1

    Remove the try and catch

    In "Await a sequence", change the try { await waiting; } catch … line to just await waiting;. In a real run the console gets [error] Uncaught (in promise) AbortError: The user aborted a request. and the lines after it never print: the async function stopped at the rejection.

  2. 2

    Cancel every animation

    In "Everything on the page", press Cancel them all. The three tracks vanish and the console prints after cancelling them all: 0 animations, but the CSS rule is still there: turn. The bar stops spinning, but the rule is unchanged, so replaying the page (or changing the class) starts it again.

Code & diagrams

Await a sequence

Each await waits for the real end of the animation. The last two lines use a helper that turns finished and cancelled into true and false, so nothing rejects unhandled.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

ready resolves with the animation: true | playState running, pending false
step 1 done
step 2 done
finished promise
onfinish event
awaiting a cancelled animation -> AbortError: The user aborted a request.
finished read after cancel() -> still pending after 5 frames
done(cancelled animation) -> false
done(finished animation) -> true
Everything on the page

One CSS transition (the blue bar's class change), one CSS animation (the spinner) and one animate() call, all in one list. The button cancels them all.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

document.getAnimations(): CSSTransition opacity | CSSAnimation turn | Animation slide
box.getAnimations(): 1 | body.getAnimations(): 0 | body.getAnimations({ subtree: true }): 3
after pausing them all: paused, paused, paused
a CSS animation is steerable like any other: true
Replace or add

All three squares have the same CSS spin. The middle one gets a scale with the default composite and the right one with add. The numbers are read with every animation frozen at 500 ms.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

a: matrix(0, 1, -1, 0, 0, 0)
b: matrix(1.25, 0, 0, 1.25, 0, 0)
c: matrix(0, 1.25, -1.25, 0, 0, 0)
composite: replace and add

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Await the finished promise of a cancelled animation

In "Await a sequence", put await c.finished; right after c.cancel(); (before the try/catch).

terminal
$ Run it and read the console
── what you'll see ──
The lines stop after "onfinish event". Nothing is printed, no error appears, and the function never continues.
From a real run: the async function hangs silently at that line.

Myth vs fact

Myth

getAnimations() only lists animations made with animate().

Fact

It lists CSS animations and CSS transitions too, as CSSAnimation and CSSTransition objects, so one call can pause, finish or cancel everything on a page.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A CSS transition only exists if the browser has a recorded starting style. That is why the example reads getComputedStyle(fade).opacity before the class change.

  • ▸

    To honour a reduced-motion switch while the page is open, finish what is running: document.getAnimations().forEach((a) => { try { a.finish(); } catch {} }). finish() throws InvalidStateError on an endless animation (Topic 8.3), so those need cancel() instead.

  • ▸

    In tests, await Promise.all(document.getAnimations().map((a) => a.finished)) waits for the page to go quiet. Infinite animations never finish, so filter them out first, or the test hangs (Phase 18).

Remember this

  1. 1

    The problem this solves: the cart flight has steps: fly, then land, then bump. Chaining them with setTimeout(…, 560) goes wrong the moment the duration changes, the tab is slow or the user presses reduce motion. A promise settles when the animation really ends, whatever its length.

  2. 2

    **ready and finished.** animation.ready resolves when the browser has started playing (one frame after animate()), with the animation itself as the value. animation.finished resolves when it reaches its end, with the animation too. await move(40).finished is a pause until it is done. In the console below, two steps run one after the other this way. The older onfinish handler still works, but the promise settles first (the example prints finished promise before onfinish event).

  3. 3

    Cancel rejects. If the animation is cancelled while you are waiting, the promise rejects with a DOMException named AbortError and the message "The user aborted a request." An await with no try/catch then throws, and without a catch it is an unhandled rejection: Uncaught (in promise) AbortError. Pages cancel animations in ways you do not write (a library, getAnimations().forEach(a => a.cancel())), so wrap awaits, or use the helper below that turns the outcome into true or false.

  4. 4

    A trap after cancel. Read anim.finished *after* cancel() and you get a new promise that is neither resolved nor rejected: the example shows still pending after 5 frames. It settles only if the animation is played again. Awaiting it hangs your function silently, with no error. So grab the promise before the cancel, or check playState === "idle" first.

  5. 5

    **getAnimations().** document.getAnimations() returns every animation on the page, including CSS animations and transitions (as CSSAnimation and CSSTransition objects, which are Animations), in this order: transitions, CSS animations, then script animations. element.getAnimations() returns those on one element, and { subtree: true } includes its descendants (document.body.getAnimations() is empty, { subtree: true } found all 3). Use it to pause everything when a modal opens, or to cancel on cleanup.

  6. 6

    Cancelling a CSS animation is not undoing the rule. cancel() on a CSSAnimation stops it, but the stylesheet rule is still there (animation-name still says turn). It only restarts when the rule changes. For a real stop, change the class too.

  7. 7

    **composite.** When two animations touch one property, the default (replace) means the newer one wins and the older one is hidden. composite: "add" adds the newer one's effect on top of what is underneath, "accumulate" is similar for values that sum. A spinning element (a CSS rotate animation) with a WAAPI scale bump: with replace the spin stops while the bump runs (the middle square), with add it keeps spinning and the scale applies on top (the right square).

Explain it without notes

01

Why does await anim.finished sometimes throw, and how do you write code that copes?

02

What does document.getAnimations() return that surprises people?

03

What does composite: "add" fix?

Practice

01

Write playSteps(el, steps) that runs an array of keyframe objects one after another and resolves true if all finished, false if any was cancelled.

02

Write pauseAll() and resumeAll() for the whole page, leaving animations that were already paused alone on resume.

Done when you can

  • You can sequence animations with await finished

  • You can handle the AbortError of a cancelled animation

  • You know that finished read after cancel() never settles

  • You can list and pause everything with getAnimations() and use composite: add