 Hi, Andrew. How's it going? Hey, Damien. I'm doing well. What do you have for our audience today? Today, we're going to talk about workers, and in particular, two types of them, web workers and service workers, their similarities, differences, the most popular communication APIs, and some common use cases. What about you, Andrew? I'll have some real-world examples of how these spans are used by site and web apps for speed, reliability, and better user experience. Great. Let's dive right into it. To begin, let's start with a quick recap on multi-threading in the web. The browser uses a single domain thread to run all the JavaScript in a web page, as well as to handle user inputs and events, and do things like rendering the page and performing garbage collection. In platforms like Android and iOS, there's also the concept of a main thread, also called the UI thread. Blocking the main thread for too long can result in warnings and errors. For example, in the latest versions of Android, not responding to events for five consecutive seconds is not only bad for the user experience, but it directly produces an application crash. The web is not as strict as native platforms in this aspect, but occupying the main thread for long periods of time certainly has an impact in the user experience. We recently introduced the concept of core web vitals, a set of quality signals to achieve great user experiences in the web. Expensive JavaScript code can lead to more users perceiving some of these metrics as poor, especially in resource-constrained devices. For example, running blogging JavaScript at the beginning of a page load can delay rendering, leading to a slower, largest contentful pane. But perhaps the aspect that could suffer the most is interactivity. Blocking the main thread can prevent the browser from receiving and processing user events, leading, for example, to a higher first input delay. Native applications also offer ways of executing long-running tasks by heavy downloads in the background with the independence of the user interface. In the web, running that code directly in the page might not only block the main thread, but will also interrupt the task if the tab is closed or the user navigates away from the page. Unlike native app languages, JavaScript was designed around the concept of a single thread and lacks capabilities needed to implement a multi-threading model like the ones that have how. Despite this, a similar pattern can be achieved in the web by using workers, which is the way of running scripts in background threads without interfering with the user interface. We have topped several times about two types of workers, web workers and service workers. They not only sound similar, but they actually have some things in common. First, both running a secondary thread, allowing you to execute JavaScript code without blocking the UI. Second, they don't have access to the window or document objects. Therefore, they can't interact with the DOM directly and have limited access to browser APIs. Third, they can communicate with the page via asynchronous communication APIs. Finally, both types of workers are currently supported by all major browsers. Due to these similarities, one might think that anything that can be done by a web worker could be done in a service worker, but there are important differences too. The first is that a service worker can listen for certain specific events, like fetch to intercept network requests and push events in the background via the push listener. These are not tasks for a web worker. The second has to do with the relationship with the page. While an instance of a web worker belongs to a single page, a page can spawn multiple web workers. For example, image compression web app Squash uses two web workers to apply independent optimizations to each side of an image without blocking the UI. Service workers are the opposite in this aspect. A page or tab is controlled at most by a single service worker, but the service worker can control multiple active tabs for a given scope. The final difference has to do with their lifespan. A web worker is tightly coupled to the tab it belongs to. For that reason, closing the tab where the web worker is running will terminate the web worker as well. In the case of a service worker, their lifespan is independent. Closing all its active tabs might not mean that the service worker will get killed. This allows the service worker to continue running tasks in the background with independence of the user interface. APIs like background fetch, for example, let the service worker process long tasks like large media downloads in the background without the need of any active tabs. The difference between both types of workers suggests in which cases one might want to use one or the other. Use cases for web workers are more commonly related to offloading work, heavy computations to a secondary thread to avoid blocking the UI. Video games like Procs use web workers to upload expensive game logic to a separate thread to achieve a more responsive user interface. Service worker tasks are generally more related to acting as a network proxy and things like caching and offline. In a podcast PWA, for example, one might want to allow users to download complete episodes to listen to them while offline. These tasks can be delegated to a service worker. That way, if the user closes the tab while the episode is downloading, the task doesn't have to be interrupted. Before moving into production examples, a final aspect to consider about these two types of workers is how they can communicate with the page. At the higher level, communication always follows the same pattern. One context calls the other via the post-message interface. The other end implements an on-message handler to receive and process the request. And the same pattern can be applied in the opposite direction. As we'll see, depending on the type of worker, there are more specific APIs on top of these methods to lend to implement the most common use cases. Let's start with communication with web workers. As we have learned, an instance of a web worker is tightly coupled to a single page or tab. For that reason, communication in two-way communication with the page is relatively straightforward. First, the page creates a worker object and communicates with it via post-message. The worker implements an on-message listener to receive and process the message. To communicate back with the page, it can directly call post-message and send a response. Finally, the page implements an on-message listener to receive the response from the web worker. Window and service worker communication can be more complex. For the page, identifying the service worker associated with it is not as complicated, because a path or route can be controlled at most by a single service worker. But as we have seen, one service worker can control multiple active tabs at the same time. This is the term you first to which client to communicate to. There are different APIs that can be used to that end. In this talk, we'll cover three of them. Broadcast channel, client, and message channel. The first API is BroadcastChannel, which allows basic communication between browsing contexts like tabs, frames, web workers, and service workers. In this API, each context subscribes to a channel, by instantiating a BroadcastChannel object with a given ID. Messages are sent and received via the interface that BroadcastChannel provides. The code is the same for any context, like pages and service workers. First, instantiate a BroadcastChannel object with a given ID. To send messages to the other context, call post-message on the BroadcastChannel object. You can optionally use a message ID to identify the type of message or operation that you are invoking. Similarly, to listen to messages, implement an on-message listener on the BroadcastChannel object. As we have seen, BroadcastChannel API is a simple way of implementing one-way communication without referring explicitly to any particular context. At the moment of this talk, this API is supported by most of the major browsers, except Safari. Our next API is the Client API. This API provides an interface to the service worker to obtain an array of all the active tabs ordered by the last focused tab. That way, the service worker can iterate and communicate directly with any client. For pages, communicating with the service worker is simple. They can call post-message in the service worker interface. Similarly, they can listen to messages by implementing an on-message listener on the service worker interface. To communicate back with the page, the service worker first obtains an array of all the active clients. Then it can send messages using the post-message method on each client object. In this example, we send a message to the last focused tab, which can be accessed from the first position of the array. As you can see, the Client API makes it really easy to communicate with different tabs. In fact, this is one of the techniques that Surma and Jake recently explored as a way of getting multiple active tabs synchronized through a service worker. Client API is available in most browsers, but not all its methods are. So make sure to check browser support before using a particular functionality. Our final API is MessageChannel. This API requires an initial configuration step to define and pass a port from one context to another to establish a two-way communication channel. The page instantiates a MessageChannel object and uses post-message to send a port to the registered service worker. The service worker receives the message and saves a reference to the port. Later, it can communicate with the page by calling post-message through the reference of the port. Finally, the page can listen to messages by implementing a non-message handler in the MessageChannel object. MessageChannel is the only API that counts with support from all the major browsers. So, as a quick recap, we have seen three ways of implementing communication between contexts. Each of them uses the post-message interface and offers more or less flexibility depending on the use case. Take into account that support can vary across browsers, so make sure to check if an API or method is available before implementing a functionality in your site. These APIs can help you implement window and service worker communication in many cases, but there are even more specific APIs for certain scenarios. For example, background sync and background fetch. Background sync lets you defer actions until the user has stable connectivity. This is useful for ensuring that whatever the user wants to send is actually sent. For example, Google Search uses this API to retrain fake queries and inform the result to the user via push notifications. Background fetch is another advanced way of calling the service worker. This API is useful if you want to download something that might take a long time, like a movie, podcasts or levels of a game. A final suggestion around communication. As you might expect, there are libraries that can be used to abstract many details of this process and help you implement these patterns more easily. For web workers, this API is the library that the examples we saw like procs and squash use. For service workers, Wordbox contains some modules that can help you implement the most common use cases. In the rest of this talk, we'll focus mostly on cases for page and service worker communication. But if you want to know more about web workers, check out Turma's talk at Trondep Summit 2019. Next, Andrew will share some patterns of page and service worker communication with a focus on reliability, speed and other use cases. Thank you Damien. In this section, we'll take a look at the communication patterns and their real-world examples. The first pattern is window to service worker communication. For this pattern, a common use case is imperative caching. With imperative caching, the page can try to accomplish two things. One, to fetch the assets ahead of time by anticipating which link the user want to click on. And two, to enable the content on the current page to be accessible even when the device is offline. To implement this pattern, we'll use the post-message API. This powerful API allows sending and receiving of text, blobs or even memory objects as message between the window and the service worker. On the page, we can curate a list of URLs to be imperatively cached based on certain conditions such as the scroll position on the page or the links visible inside the viewport. Then we'll pass the list over to the service worker to be fetched and stored in the browser's cache. This allows the page to offload heavy lifting of fetching and caching to the service worker, freeing up the main thread to handle more important tasks such as user input. By doing this, we actually achieve parallel computing in the browser. Pretty awesome, isn't it? Let's take a look at a real-life example of this pattern. 1800flowers.com a very popular floral and gift e-commerce site here in the United States. Use the service worker to imperatively cache product data to speed up user navigation. When a product listing page such as this one is loaded, the top 10 product items on the list are sent to the service worker for pre-fetching. Also, whenever the user miles over a product item, the page will ask the service worker to fetch the data for that item. Upon receiving the product IDs from the page, the service worker will fetch those products JSON files over the network. So when the user eventually clicks on one of those pre-fetched items, the data will be available locally already in the browser's cache for immediate consumption. Here's how to implement the similar behavior of imperative caching. On the page, first we want to make sure the service worker is activated and ready to receive messages. Then we'll use the post-message API to send a message. Now the following part is specific to the imperative caching purpose. Inside the message, we put a list of URLs in the JSON object, along with a pre-fetched type to indicate that the intent is to ask the service worker to pre-fetch these URLs. On the service worker's site, first we create an event listener to handle messages sent from any pages in this service worker scope. Then if the message is an instruction to pre-fetched, we iterate through the list of URLs. Here we use a helper function called fetchAsync to encapsulate operation. The next pattern is service worker to window communication. A common use case is to broadcast updates that take place on the service worker's site. For example, to inform the pages when a newer version of the service worker has been installed, then the pages can take action so that the user can write away from the updates. Or to let the user know that certain content has been cached locally for offline access. The web app's capability to work offline is powered by its service worker. Therefore, when the service worker is activated and has downloaded all the necessary assets to be used offline, it notifies the web app. Upon notification, the web app can subsequently display a pop-up informing the user that it is ready to be used offline. Let's take a look at another example. Tinder, a popular geosocial networking and online dating site here in the US and worldwide, also utilizes this service worker to window communication pattern. When there's a newer version of the service worker activated, the Tinder web app will be notified by the service worker and displays a button to encourage the users to update. One way to achieve the same user experiences in the examples is using the broadcast channel API as Damian mentioned earlier. In the service worker code, we can create a broadcast channel object with a channel ID that will be listened to by the subscribing pages. And then we post messages to that object. Inside the message we will send an instruction type called service worker updated. In the page code, we also create a broadcast channel object with the same ID. Incoming messages can be handled by the onMessage event handler. Here, since the instruction in the message received is service worker updated, we will show a prompt to the user asking to refresh. Combining the previous two patterns will come to our final pattern of bidirectional communication. A well-designed bidirectional communication allows the service worker to be used to its full potential by taking the advantage of its parallel processing nature and its independent lifespan. Imagine this podcast web app in which the user chooses episodes to download and listen to. Because of the file size can be large, downloading directly on the page may block out the important tasks on the main thread. And if the user closes the app before the download is completed it will have to start over again when the user comes back. But if we delegate the downloading to the service worker, both problems can be solved. Whenever an episode is selected the web app can instruct the service worker to fetch the episode. While the service worker is helping per request, the main thread can take care of other things. And if the app is closed prematurely the service worker can still finish the job in the background because it is still within the service worker's lifespan. Once the fetching is completed the service worker can then message the app to take action. For example, changing the icon so that the user can start playing the episode. This pattern can be further enhanced by the background fetch API in which even the service worker can further delegate the download to the operating system. With the operating system handling the download the user will be given more control such as pausing, resuming and cancelling the process outside of the app. In this talk we explored the differences between a web worker and a service worker. How a service worker can help improve UX. The communication patterns between window and service worker. And the real world examples of how these patterns can be applied. There are endless possibilities to make the service worker dance nicely together with your site or your web app. Give these ideas a try. Take them to the next level and let us know about your innovative ways of using it. Thank you and see you next time.