new improvements for web developers - frontend.fi, helsinki
TRANSCRIPT
Web improvements @robertnyman
Promises
fulfilled = The action relating to the promise succeeded
rejected = The action relating to the promise failed
pending = Hasn't fulfilled or rejected yet
settled = Has fulfilled or rejected
var promise = new Promise(function(resolve, reject) { // do a thing, possibly async, then… if (/* everything turned out fine */) { resolve("Stuff worked!"); } else { reject(Error("It broke")); }});
promise.then(function(result) { console.log(result); // "Stuff worked!"}, function(err) { console.log(err); // Error: "It broke"});
fetch()
XMLHttpRequest example
function reqListener() { var data = JSON.parse(this.responseText); console.log(data); } function reqError(err) { console.log('Fetch Error :-S', err); } var oReq = new XMLHttpRequest(); oReq.onload = reqListener; oReq.onerror = reqError; oReq.open('get', './api/some.json', true); oReq.send();
fetch() version
fetch('./api/some.json') .then( function(response) { if (response.status !== 200) { console.log('Looks like there was a problem. Status Code: ' + response.status); return; } // Examine the text in the response response.json().then(function(data) { console.log(data); }); } ) .catch(function(err) { console.log('Fetch Error :-S', err); });
Response metadata
fetch('users.json').then(function(response) { console.log(response.headers.get('Content-Type')); console.log(response.headers.get('Date')); console.log(response.status); console.log(response.statusText); console.log(response.type); console.log(response.url); });
Response Types
basic
cors
opaque
Defining modes
same-origin
cors
cors-with-forced-preflight
no-cors
Defining modes
fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'}) .then(function(response) { return response.text(); }) .then(function(text) { console.log('Request successful', text); }) .catch(function(error) { log('Request failed', error) });
Service Workers
It's a JavaScript Worker, so it can't access the DOM directly. Instead responds to postMessages
Service worker is a programmable network proxy
It will be terminated when not in use, and restarted when it's next needed
Makes extensive use of Promises
No Service Worker
HTTPS is Needed
Register and Installing a Service Worker
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); });}
chrome://inspect/#service-workers
// The files we want to cachevar urlsToCache = [ '/', '/styles/main.css', '/script/main.js']; // Set the callback for the install stepself.addEventListener('install', function(event) { // Perform install steps});
Installing
Inside our install callback:
1. Open a cache 2. Cache our files 3. Confirm whether all the required assets are cached or not
Install callback
var CACHE_NAME = 'my-site-cache-v1'; var urlsToCache = [ '/', '/styles/main.css', '/script/main.js']; self.addEventListener('install', function(event) { // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) );});
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); } ) );});
Caching and Returning Requests
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit - return response if (response) { return response; } // IMPORTANT: Clone the request. A request is a stream and // can only be consumed once. Since we are consuming this // once by cache and once by the browser for fetch, we need // to clone the response var fetchRequest = event.request.clone(); return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; } // IMPORTANT: Clone the response. A response is a stream // and because we want the browser to consume the response // as well as the cache consuming the response, we need // to clone it so we have 2 stream. var responseToCache = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); }); return response; } ); }) );});
Caching new requests cumulatively
Updating a Service Worker
1. Update your service worker JavaScript file. 2. Your new service worker will be started and the
install event will be fired. 3. New Service Worker will enter a "waiting" state 4. When open pages are closed, the old Service
Worker will be killed - new service worker will take control.
5. Once new Service Worker takes control, its activate event will be fired.
Updating a Service Worker
Cache management & whitelists
self.addEventListener('activate', function(event) { var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1']; event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { return caches.delete(cacheName); } }) ); }) );});
Push notifications
<button class="js-push-button" disabled> Enable Push Messages </button>
// Are Notifications supported in the service worker? if (!('showNotification' in ServiceWorkerRegistration.prototype)) { console.warn('Notifications aren\'t supported.'); return; }
// Check the current Notification permission. // If its denied, it's a permanent block until the // user changes the permission if (Notification.permission === 'denied') { console.warn('The user has blocked notifications.'); return; }
// Check if push messaging is supported if (!('PushManager' in window)) { console.warn('Push messaging isn\'t supported.'); return; }
// We need the service worker registration to check for a subscription navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) { // Do we already have a push message subscription? serviceWorkerRegistration.pushManager.getSubscription() .then(function(subscription) { // Enable any UI which subscribes / unsubscribes from // push messages. var pushButton = document.querySelector('.js-push-button'); pushButton.disabled = false; if (!subscription) { // We aren't subscribed to push, so set UI // to allow the user to enable push return; } // Keep your server in sync with the latest subscriptionId sendSubscriptionToServer(subscription); // Set your UI to show they have subscribed for // push messages pushButton.textContent = 'Disable Push Messages'; isPushEnabled = true; }) .catch(function(err) { console.warn('Error during getSubscription()', err); }); });
{ "name": "Push Demo", "short_name": "Push Demo", "icons": [{ "src": "images/icon-192x192.png", "sizes": "192x192", "type": "image/png" }], "start_url": "/index.html?homescreen=1", "display": "standalone", "gcm_sender_id": "123456789012", "gcm_user_visible_only": true }
<link rel="manifest" href="manifest.json">
Add to Homescreen
Cache management & whitelistsApp Install Banners
App Install Banners prerequisites
You have a web app manifest file
You have a service worker registered on your site. We recommend a simple custom offline page service worker
Your site is served over HTTPS (you need a service worker after all)
The user has visited your site twice over two separate days during the course of two weeks.
"You can do all of this?"