Fiddle-of-the-week: await WebRTC in Firefox 52
This isn’t a post about a new WebRTC feature. Rather, we’ll explore the new async
/await
JavaScript syntax in the just-released Firefox 52 and Chrome! Two new keywords let us write asynchronous code the same way we’d write synchronous code. WebRTC APIs, as you’re probably aware, are quite asynchronous. Luckily they support promises – a requirement to use this new syntax – and it just works! I’ve always found callbacks and even monadic promise-chains hard to follow, but now we can write short, yet meaningful code that gets stuff done.
“Big deal” you say, “maybe I’ll be able to use this in production in 2027!” Well, it turns out you can use Babel to transpile this down to code IE8 will understand. But even if you’re not planning on doing that, I find learning how to write this way helps me reason about complicated WebRTC behaviors more easily. The code now matches how we talk about it, a boon to understanding and communication; it’s my new whiteboard go-to. It might not work for you, but let’s try it:
Below is an iteration of last week’s Fiddle-of-the-week on getStats
rewritten to be opened in two adjacent tabs, using async
/await
. Transmitting across tabs is not only more impressive, but also more real-world-like: we only deal with one side and signal the other. The example also uses destructuring, in addition to template strings from last week, two other modern JS features, to keep it interesting.
There are two parts. The immediately visible part covers connection entirely. Later, scroll down to see the getStats
loop.
To see it in action, first right-click here and open this post in a second window. Next, activate the “Result” tab in both windows, and finally come back here and hit the “Call” button!
This works in Firefox 52 and Chrome Canary. You should see it ask for your camera and transmit the video to the other window, as well as a running tally of statistics on both ends.
Highlights
Look how straightforward making an offer is now:
await pc.setLocalDescription(await pc.createOffer());
sc.send({sdp: pc.localDescription});
It’s written as we’d explain it: “We set our connection’s local description to an offer, and send it to the other side on our signaling channel.” It becomes a detail that some steps are asynchronous (even though they’re still clearly marked).
Scrolling down to the next function above, it’s our loop for polling of statistics:
while (true) {
let html = "", stats = await pc.getStats();
for (let stat of stats.values()) {
if (stat.isRemote) continue;
switch (stat.type) {
case "outbound-rtp": {
html += dumpOutbound(stat) + "<br>";
let rtcp = stats.get(stat.remoteId);
if (rtcp) html += "RTCP " + dumpInbound(rtcp) + "<br>";
break;
}
The code is no longer structured around synchronization points, getting straight to the meat of building our output. Here’s a cheat-sheet on Maplike behavior, if you need it.
Notice we use a for-of
loop instead of forEach
. The importance of doing so isn’t immediately obvious here, but it’s worth calling out: Should we later need to use the await
keyword inside the loop, it’ll work right! forEach
on the other hand, would betray us, as it takes a function, and doesn’t know about async
functions. It’s also nice to use continue
and return
again, a revival of iterative programming.
await wait(100);
}
We sleep 100 milliseconds and loop, which importantly does not busy-wait, but uses setTimeout
(see the one-line wait
function). While setInterval
would perhaps provide more reliable timing, we avoid its error-handling traps, and avoid starving slow systems. We also abort correctly in case of error instead of leaving “running-fan log spew” to use a euphemism. But the main point here is illustration: In general, looping asynchronous sequences is wildly complicated to pull off with callbacks and even recursive promise-chains. I can’t tell you how many stackoverflow questions I’ve seen on this. Now it just works.
Best of all, errors are now handled the same for synchronous and asynchronous code, using good-old try{}
catch(){}
. They may not win points for compactness, but as much as I hate to admit it, compactness is sometimes overrated.
Planning ahead
Even though it may be a while before you can use async
/await
in your organization, know that all browsers that support WebRTC support promises now. This means that once you’ve feature-detected WebRTC, you can use promises (use adapter.js to cover older versions)!
Converting promise code to async
/await
later should be trivial. Unfortunately, a lot of WebRTC code we see on the web still uses the legacy callback APIs, which bites. So if you want to prepare, take a moment to update your code to promises now. Your future self will thank you. Safe transmissions!