Shots in this scene
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.
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
Remove the try and catch
In "Await a sequence", change the
try { await waiting; } catch …line to justawait 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
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
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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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) -> trueOne 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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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: trueAll 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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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 addBreak 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).
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).opacitybefore 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()throwsInvalidStateErroron an endless animation (Topic 8.3), so those needcancel()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
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
**
readyandfinished.**animation.readyresolves when the browser has started playing (one frame afteranimate()), with the animation itself as the value.animation.finishedresolves when it reaches its end, with the animation too.await move(40).finishedis a pause until it is done. In the console below, two steps run one after the other this way. The olderonfinishhandler still works, but the promise settles first (the example printsfinished promisebeforeonfinish event). - 3
Cancel rejects. If the animation is cancelled while you are waiting, the promise rejects with a
DOMExceptionnamedAbortErrorand the message "The user aborted a request." Anawaitwith notry/catchthen 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 intotrueorfalse. - 4
A trap after cancel. Read
anim.finished*after*cancel()and you get a new promise that is neither resolved nor rejected: the example showsstill 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 checkplayState === "idle"first. - 5
**
getAnimations().**document.getAnimations()returns every animation on the page, including CSS animations and transitions (asCSSAnimationandCSSTransitionobjects, which areAnimations), 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
Cancelling a CSS animation is not undoing the rule.
cancel()on aCSSAnimationstops it, but the stylesheet rule is still there (animation-namestill saysturn). It only restarts when the rule changes. For a real stop, change the class too. - 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 CSSrotateanimation) with a WAAPIscalebump: withreplacethe spin stops while the bump runs (the middle square), withaddit keeps spinning and the scale applies on top (the right square).
Explain it without notes
Why does await anim.finished sometimes throw, and how do you write code that copes?
What does document.getAnimations() return that surprises people?
What does composite: "add" fix?
Practice
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.
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