Promises in Web Animations
When I first started learning about the Web Animations API there was one way to handle a "finish" event - the onfinish
callback. It lets you assign it a function and that function is called when the animation is finished.
There is a newer way to handle this scenario, though... via the finished
Promise. This either excites you (if you know what a Promise is), confuses you (if you know what a Promise is), or makes you ask what a Promise is. So...
What is a Promise?
Promises have gone through several specs and implementations. A standardized version has found its way into most browsers with Edge, Firefox, Safari, Chrome, and Opera's latest all supporting Promises. They are a feature in JavaScript similar to a callback in that they look for some code to finish running and then run some other code. With callbacks we often think of success and error handlers... with Promises we think of resolving and rejecting, such as:
//set up 1 second animation to fade box out
var box = document.getElementById('box');
var animation = box.animate([{ opacity: 1 }, { opacity: 0 }], 1000);
function finishedHandler() {
console.log('animation finished: ' + Date.now());
}
function canceledHandler() {
console.log('animation canceled: ' + Date.now());
}
//the Promise version, log the timestamp when the Animation finishes
animation.finished.then(finishedHandler, canceledHandler);
//the effective successful equivalent with the onfinish callback
animation.onfinish = finishedHandler;
This code starts an animation (by calling animate()
on an element), and any other code will continue to execute while we wait for the animation to enter the "finished" state. Animations will expose a finished
Promise that will "resolve" when the animation finishes. You can think of the Promise as a future object. When it resolves (in this case, a successful finish of the animation), the first function passed into the then
will run. If the animation is rejected (the animation is canceled), it will run the second function passed to then
, if specified.
For rejection, the current spec defines it this way, but there are discussions to change away from this. We will focus on how to work with an animation that resolves for now...
One time fits all
Promises by definition will either resolve or be rejected, and this resolution will only happen once. As soon as a Promise is resolved or rejected, it will not resolve nor reject again. Since animations can be played multiple times, this seems to be at odds with the Web animations API as subsequent finishes would not resolve again.
To solve this, the spec dictates a new Promise will replace the existing finished
Promise when the play state changes to "running". This allows you to latch on to the Promise resolution state again, but you will have to add your handlers via then
again.
//extending the previous example, play the animation second time
animation.play();
//set 'then' to what spec states will be a newly created Promise
animation.finished.then(finishedHandler);
//the onfinish from before remains
Note: As of March 18, 2016 the polyfill has an issue with resolving subsequent finished Promises at the correct time.
Then Why Bother?
Imagine a case where you want to trigger new animations when a given animation finishes. The following example shows two boxes, one using onfinish
to start new animations and the second using the finished
Promise.
See the Pen Callbacks vs. Promises with Web Animations API by Dan Wilson (@danwilson) on CodePen.
Callbacks will nest, and Promises can chain by returning a Promise. For this example it reads differently, and one might be more readable than the other to you. In fuller development the chaining can provide more power with how you manage your animation states and values. We will look into that in a future post.
There is a lot more to talk about with Promises... for much fuller discussions on Promises check out the Ponyfoo article on the topic and the MDN article, especially their suggested reading.
Thanks to Rachel for triggering this discussion and Šime for pointing me to when an animation is rejected in the spec (and Jake for mentioning the discussion about changing that). Thanks also to Martin for recommending additional Promise articles.