JAKE ARCHIBALD: Hello.
I'm Jake Archibald, off of the Chrome Developer Relations
Team.
And I want to show you something pretty incredible.
Now don't adjust your set.
What we're looking at here is the browser establishment
connection.
It's an incredible feat of technology.
Data is being sent through the air despite low signal,
then through cabling to the other side of the world.
And yet, here comes the response making that final leap
through the air.
Oh, it didn't work.
Network connectivity is a single point of failure
when it comes to user experience on the web.
When connectivity is bad, user experience is bad.
When connectivity is gone, user experience is gone.
Native apps can overcome this.
Well-built native apps get cached stuff
on the screen straight away, and then they go to the network.
And if that fails, sometimes they
don't even need to tell the user something went wrong.
This pattern is offline first.
And I want to tell you about new API that brings it to the web.
PAUL: Isn't that what appcache is for?
JAKE ARCHIBALD: Yes, good point.
Thanks, Paul.
Appcache did try and fix this problem.
It used a man-- this is your bit, done.
You're good now.
You can go.
PAUL: Oh.
JAKE ARCHIBALD: It used a manifest format that
was really simple, but resulted in complex changes
to the networking and caching behavior of your pages.
Well, I say complex.
As developers, we deal with complex code all the time.
But in appcache's case, it feels like magic.
Because there's little to link the lines
in the manifest to the bits of this behavior it triggers.
The specific problems with appcache
are pretty well documented.
But here's a quick recap.
[MUSIC PLAYING]
Well, we should all be on the same page now.
But it's a shame about appcache, because there's
some powerful stuff in there-- caching, routing,
updating, offline first, online first, handling failure.
But those tools can only be used in very particular ways.
There's no expression of intent in the manifest.
This makes it really easy to be caught out
by unexpected behavior.
And your only line of defense is to read the spec.
Read it until your eyes bleed, and then
wipe the blood from your eyes, and read the spec again.
The solution to this is the ServiceWorker.
This is a new spec developed by Google, Mozilla, Samsung
and others.
There's the spec.
You can see why I went for the fireworks instead.
But the ServiceWorker breaks appcache down
into a set of primitives that you
can reason about and use independently.
And no behaviors change until you change them,
using JavaScript.
Google and Mozilla are aggressively
working on implementations of ServiceWorker.
Some of the things I cover may be buggy or not yet
implemented.
Check out this site for the current status in each browser.
Right, let's take a look at a real-world use case.
This is Trained to Thrill, a simple website
I threw together.
It delivers you the very latest in hot railway action,
straight from Flickr.
I'm going to add it to my home screen,
because it feels like that kind of thing
I might need urgent access to.
And it gives it that native app feel.
It's all on GitHub, so you can check out
and run the final implementation.
I don't know why I made something about trains.
It just came to me as I was standing outside,
admiring some trees.
Unfortunately, this is our current offline experience.
Things are great online.
But without a connection, we get total failure.
Let's do something about that.
In our site's code, we'll register for a ServiceWorker.
I'm using feature detection here so I don't get errors
in browsers that don't yet support ServiceWorkers.
This is the URL to the script that will control our pages.
And this is the range of pages it'll control.
Register returns a promise, so you can call .then and pass
in success and failure callbacks.
Promises are a really powerful way
to control async operations.
To learn more about promises, check out
this article on HTML5 Rocks.
Because of the control you get with a ServiceWorker,
you can only register for them on pages
that are served over HTTPS.
GitHub does this, so it's a great place
to host experiments and little apps.
So what happens to sw.js?
Well, it runs in a different thread,
and it has no DOM access.
It's a place where you can run code outside of pages
and control their loading.
One ServiceWorker can control many pages.
You can think of it as like a proxy server
sitting on the client, able to re-route requests
or satisfy them from its own cache.
Let's do some of that.
The first thing we care about is the install event.
This fires when the browser sees this version
of the ServiceWorker for the first time,
passing a promise to wait until it defines
the length of the installation process.
If the promise rejects, the install fails,
and this worker won't do anything else.
If that happens, don't worry about it.
You'll get another shot at it next time
you call ServiceWorker.register.
We're going to use the install event to cache everything
we need to load the page.
Cache is a new storage API provided by the ServiceWorker.
It's basically a programmable HTTP cache,
but things never expire.
They're only gone when you remove them.
We'll create a new cache and add it
to the cache's object to give it persistence.
The cache's object is a global within ServiceWorker.
Then we'll add items to the cache, everything
we need to load the site.
These methods return promises so they can be used to feed
[? wait ?] [? until. ?]
If any of these things fail to cache,
the promise rejects and the install fails.
So if the install succeeds, we know
we have all of these items in the cache.
Although we've cached a lot of stuff,
the browser won't use any of it until we tell it to.
You can listen for the fetch events.
This fires for every page request
within the ServiceWorker scope, but also
for requests made by those pages,
even if they're to other domains.
You get a lot of information about the request,
such as URL, method, headers.
However, the most interesting bit is you can hijack the event
and respond to the request yourself.
RespondWith takes a response object,
or a promise for a response.
You could create the response yourself
from scratch using the response constructor.
But it just so happens we've got a cache full of stuff
we can use.
Caches.match takes a request and gives us
a promise for a response, a response that
matches the request.
The matching is done by URL method
and vary headers, just like the HTTP cache.
Let's see how that runs.
Oh, we've broken it.
Connectivity derailed.
Because it's trains.
Even with full connectivity, it's broken.
In fact, if we take connectivity away,
we get exactly the same results.
Our page is working offline, but all communication with Flickr
is failing, even online.
This is because our request to Flickr,
like all in-page requests, hit the fetch event.
And then it tries to get a response from the cache,
doesn't find one.
The promise rejects, and we get back
what looks like a connection failure.
You see, we've told it to satisfy all requests
from the cache.
And we didn't cache anything from Flickr.
Thankfully, promises let us recover from this.
If our cache lookup fails, we can
use event.default, which goes to the network
to fetch the resource.
That might fail as well, of course, in which case
we can catch again and serve a fallback.
And that will never fail, because we depended on fallback
to HTML in our install step.
Now everything works again, well,
as long as you've got a connection.
If we disable connectivity, we're back here.
So we're pretty close.
We just have to cater for Flickr's absence
when we're offline.
But to do that, before we do that, let's take a step back.
This is what we have so far.
And it just so happens that we're still roughly
within appcache's capabilities.
But to do this in appcache, our manifest would look like this.
It looks a lot simpler, but it's not actually simpler.
It's just fewer characters.
This doesn't involve us in all the behaviors it's created,
the caching, the routing, the fallbacks.
If this Service Worker does something we don't expect,
if it behaves in a way we don't understand, what can we do?
Well, it's JavaScript.
We can use console.log.
We can open dev tools, set breakpoints,
see the state of everything as it goes through the worker.
If this does something we don't expect, and it probably will,
we're back to reading the spec again, crying blood.
But enough about appcache.
Let's do something it can't.
Let's make that Flickr data and imagery work offline.
Here's what our page is currently doing.
We request photo data from Flickr.
And if that works, we update the page with that data.
Otherwise, we show an error.
Either way, we stop that spinner spinning around.
We want this to work without a connection, which obviously
means caching Flickr's API response and images.
However, if the user's online, we
want them to get the very latest locomotive
delights Flickr has to offer.
It's tempting, then, to go for something
like this, where we try to get train photo data online.
And if that fails, we go for the cached stuff.
For some apps, this is the best approach.
But it's not good enough for our trains.
You see, this works fine when you're
in a big city with full signal.
Stuff arrives fast.
It works fine when you're on the underground, with no signal.
In fact, it's even faster than before because we fail straight
away and go straight to the cache.
This is the problem case.
Your phone sort of, kind of, maybe has a connection,
possibly.
It'll either take ages to get the data,
or take ages to decide it can't.
In fact, signal bars aren't even a good predictor
of this behavior.
We can have full signal, but we're still
beholden to all of the internet pipes that
sit between us and Flickr.
Prediction is really inaccurate when it comes to the network.
Take navigator.onLine, which is available within ServiceWorkers
as well as pages.
But it's not all that useful.
You see, it tries to be predictive.
When you have absolutely no connectivity, it will be false.
When you have some connectivity, it will be true.
Even if the route you're connected to
is just plugged into some soil, it'll be true.
Now our prediction is flawed, be wary of it.
With this code, the network needs
to succeed or fail before we do anything.
Instead, let's get stuff from the cache straight away
and then go to the network.
This isn't a revolutionary idea.
Native apps often do this.
But now we can do it on the web.
First, we change how we fetch live data slightly.
We stop handling errors.
And we set a variable to true if and when it completes.
We make this request race with another,
one that's only going to go to the cache.
It sounds unbelievable that the network might beat the cache.
But some virus scanners make disk access
like wading through mud.
So we should prepare for that.
In terms of error handling, we only
want to show a connection error if both the cache
and the network fail to deliver.
If the network fails but the cache delivers, that's OK.
We can fail silently.
That's a good offline experience in this case.
But how do we make one request to cache and another
to the network?
Here's how we make a network request.
The Flickr search function is just
going to use XHR to make a get request to Flickr.
Getting cache data is a little bit different.
It fails if ServiceWorker isn't supported,
or if there isn't one being used by this page.
The Flickr search is almost the same.
The only difference is this additional header.
It looks like magic, but it isn't.
In fact, it doesn't do anything yet.
But it's something we'll see as part of the request
to the ServiceWorker.
And we'll add our own magic in there.
Back into ServiceWorker, let's do some extra stuff
in our fetch listener.
If the request URL is to Flickr's API,
we'll do something special.
If the request URL is to Flickr's image service,
we'll do something else special.
First, let's look at how we handle API requests.
If the request has that header, the one that we set before,
we'll send back a match from the cache only.
If there's no match, the request will fail.
That's fine.
That's exactly what we want.
If that header isn't there, we'll
create a new cache for our Flickr content
and add it to our cache's object.
Then we'll pipe the request to our cache,
but also back to the browser.
The fetch method takes a request and returns
a promise for a response.
Although we're using this request object twice,
the browser is smart enough to only make
one request under the hood.
This dynamic handling of requests and caches is
something appcache simply cannot do.
So that's API request sorted, but what about the request
to Flickr's image service?
First up, we'll try and serve the images
directly from the cache.
But if that fails, we'll get hold of our content cache,
and we'll add this new image to it.
We'll also respond with it, send it straight back
to the browser.
This exposes another interesting feature of ServiceWorkers.
Images from Flickr do not have cause headers.
You can't fetch them with XHR.
ServiceWorkers don't have this restriction.
You'll get back an opaque response.
You can't query its content with JavaScript,
but you can add it to caches and use it in responses.
And that's it.
Now we have an app that loads blindingly fast and updates
with the latest content, if the user has a connection,
but also works entirely without one.
They'll just get the last set of images they saw.
It's fully offline first.
We assume nothing at the network.
We render with what we've got, and then we
try the network and deal with the outcome.
Mission accomplished.
And that's just one example.
ServiceWorker gives you the primitives
to do what's best for your app.
It doesn't force you down a particular path.
This is a very new feature, but we're already
seeing other specifications extended
to make it even more powerful.
For example, background sync.
It adds a sync event to ServiceWorkers.
This allows a ServiceWorker to spin up even
when the user isn't at your site,
giving an opportunity to update caches.
Ideal for sending that message that failed to send,
or updating a set of train photos from Flickr.
And push-- push allows the Service Worker
to spin up in reaction to a server push message.
You can use this to update caches and notify
the user they have a new message, or, I don't know,
something to do with trains.
Anyway, ServiceWorker isn't limited to trains.
Hopefully you've got better ideas.
The W3 web mobile group are currently compiling examples,
and they're looking for contributors.
If you want to start writing demos, or coming up
with use cases, this is a great place to do it.
Making stuff work offline is just one possibility.
You can also use Service Workers to fine-tune performance
on powerful network APIs such as client hints.
If you're building with ServiceWorkers,
remember to use this page to see what's actually working.
See what you can build in Chrome today.
Of course, there will probably be bugs.
Please, file tickets as you find them.
Filing bugs and making examples will improve our implementation
and steer the direction of the API.
We're crazily excited about the performance benefits
and new features this brings to the web.
Let's build some cool stuff with it.
Thanks for listening.