Web Workers



Web Workers

0 0


brooklynjs-2016-02

Slides for Nolan's Web Workers talk

On Github nolanlawson / brooklynjs-2016-02

Web Workers

I like the way you work it

@nolanlawson

Speaker notes are in the dev console!
Hi everybody.

@nolanlawson

I'm Nolan Lawson. Sometimes I make "here's how big of a sub sandwich I had for lunch" hands, or as Mariko taught me they're called in Japanese, "pottery hands" (ろくろ回す手).
I work at Squarespace, and I help maintain PouchDB.

The web has a problem :(

I have to start us off on kind of a dour note, by pointing out that the web has a problem. It's not a new problem – it's actually been around since the beginning of the web, but it's an interesting problem, and it's going to motivate the rest of my talk.
This problem is pretty easy to reproduce. All you need to do is make a web page with an animated gif - just a simple web page! Then fire up your dev console and enter some long-running JavaScript operation, like this loop. What you'll notice is that the GIF is blocked for the entirety of the loop. You can reproduce this in all the browser, by the way: Chrome, Firefox, Safari, and IE.
But it doesn't just affect animated GIFs - it affects almost everything on your page that moves. HTML5 video, JavaScript animations, CSS animations (except for hardware-accelerated ones) will all be stopped. In fact, it's worse than that - any form elements on the page will be non-interactive as well. Note that I can't click on any checkboxes or buttons while this is running.

Every line of frontend JS you've ever written has (momentarily) stopped your page from working.

Of course, the reason for this is that JavaScript is single-threaded, and the browser environment (i.e. the DOM) is single-threaded. So while JavaScript is running, the browser can't do anything else. That's kind of a mind-blowing thought, right? It means that every line of JavaScript you've ever written has (momentarily) stopped your page from working. (Frontend JavaScript, anyway.) We should feel humbled by this fact. We have this great responsibility with the JavaScript we use.
Now, you might not have ever written *such* a long-running JavaScript operation that you blocked the page entirely. But have you ever made or seen a page that looked like this? It's functional, but it feels kinda... off. I can interact with the page, but occasionally the scrolling locks up, and the animations might briefly jerk for a few split-seconds. It works, but it doesn't feel *good*. Now, this isn't due to one big long-running operation - this is due to lots of little operations that add up over time. It's death by a thousand cuts. Why does this happen? Well, we can explain with MATH!

60 frames per second (FPS)

Most monitors will refresh at 60 frames per second. What that means is that, every second, they flash an image 60 times, which gives the illusion of movement. It's just like this flipbook.

1000ms / 60 frames = 16.7ms

If you do the math, and divide 1000 milliseconds by 60 frames, you get 16.7 milliseconds. What that means is that, any JavaScript you do has to complete in under 16.7 milliseconds if you want to stay at 60 FPS.

Factor in browser overhead...

If you factor in browser overhead, according to Paul Lewis on the Chrome team, this is actually even smaller. 10ms for all your JavaScript to execute! That's tiny! that's punishingly small! And if you think it's bad on desktop, try mobile, where we have slower CPUs.

WEB WORKERS!

So what are we supposed to do? Well, it turns out the web has a solution to this, and it's called web workers!
// index.js

var worker = new Worker('worker.js');
worker.postMessage('ping');
worker.onmessage = function (e) {
  console.log(e.data); // 'pong'
};
// worker.js

self.onmessage = function (e) {
  console.log(e.data); // 'ping'
  self.postMessage('pong');
};
I'm not going to go too deep into this code, but a web worker is basically a background thread that you can spawn whenever you want to do a bit of JavaScript work. Anything that runs inside the worker runs on another thread (another process in fact), so it cannot possibly block the DOM.

UI thread (main thread)

See, the way that UIs work, on both the web, Android, and iOS, is that you have one thread, called the main thread, whose responsibility is to respond to UI events like clicks and scrolling, and to paint. This UI thread is pretty busy. You'd be busy too, if somebody asked you to paint something every 16 milliseconds! Plus, we're sending down our giant 500KB JavaScript app, which we also expect him to run within those 16 milliseconds.
So what'd be nice, is if the UI thread could spawn some friends to help him out. Then those friends could work independently, and he wouldn't be trying to do so much himself.

Four cores

This is great, because we can actually spawn as many workers as we want, and even low-end Android phones sold today have 4 cores. So potentially we can do many things at once, in addition to the UI. When we want to do something that's unrelated to UI - business logic, reading from local storage, talking to the network, we shove that off in a background thread.

Web Workers can do...

  • Ajax
  • IndexedDB
  • Anything JS can do
  • ...but not DOM
  • Virtual DOM!
So what can you do inside of a web worker? Well, you have ajax and IndexedDB, and like I said, those actually *can* block the DOM, so it's worthwhile to put them in a web worker. You can also do any normal JS operations, except for the DOM. But hey, you can do Virtual DOM, right?
So I experimented with this, and I demonstrated that it's possible. I wrote an app with an architecture where every UI event basically gets fired off as an action to the web worker. Inside the web worker, it generates the Virtual DOM tree, diffs it with the previous tree, and then sends the patch (i.e. the minimal set of operations to be applied to the DOM) back to the main thread.
This is great that the patch tends to be small, because the *only* cost you pay with a web worker on the main thread is the cost of serializing and de-serializing the messages sent back and forth with the worker.

Pokedex.org on a 2011 Galaxy Nexus

This works surprisingly well. Here's my Pokedex.org app running on a 2011-era Galaxy Nexus phone. It runs at 60fps, and is nearly indistinguishable from a native app. On newer Android phones, it *is* indistinguishable from a native app.

React with Web Workers

And if you still don't believe me, there was a blog post recently where Parashuram demonstrates that you can run React's virtual DOM inside of a web worker, and he actually demonstrated that this resulted in a better framerate than when you don't use a worker.

Thank you!

nolanlawson.github.io/brooklynjs-2016-02

This think this stuff is going to start popping up in frameworks. It's already going to appear in Angular 2, which is where I got the idea. But you can use web workers yourself today. The APIs are very easy to work with, and there are polyfills. So if you have any JavaScript on your frontend that can run in a worker, please consider throwing it in a worker! Your framerate will improve, and your users will thank you. Thanks!