JavaScript for Progressive Web Apps

JavaScript for Progressive Web Apps

This post is to give a quick recap on some JavaScript features such as the Fetch API and arrow functions, so that you can get to work on the other PWA posts and write your own service worker!

Arrow functions

An arrow function expression is a shorter method of writing a function expression and do not have their own this binding, which makes life a lot simpler when handling events.

// function expression
function (param) {
    return param;
}

// arrow function expression
(param) => {return param}

// other arrow functions
() => {return true}
i => i + 2
(i, j) => (i + j)

In the above example there is no functional difference in the code! In the further examples, you can see that without parameters, you must pass empty parenthesis. However if there is only one parameter, you don't need to wrap it in parenthesis. It's best practice to always wrap your parameters in parenthesis just to be safe! Arrow functions implicitly return their value, that means we don't actually need to write return if all we're doing is returning a value.

In and of itself, arrow functions aren't a game changer but as we get into promises you will see just how helpful they are in making your code more readable.

Promises

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. It's something of a placeholder while your async operation runs, allowing you to wait for the Promise to complete before you try doing anything with the value it gives you.

var promise = new Promise((resolve, reject) => {
    // you could even call your own async function here instead
    if (everything_is_ok) {
        resolve("Everything is ok!");
    } else {
        reject("Everything is not ok!!!");
    }
}).then(response => {
    console.log(response);
}).catch(error => {
    console.error(error);
});

In my simple example above, you can see that the promise will resolve or reject depending on the value of everything_is_ok. However after the promise is fulfilled, I've chained together some functions.

The then and catch functions are follow-ups to the promise completion and they will be executed in order, unless an error is thrown in which case it will jump to the next catch in the chain.

You can chain these functions together to allow for some simple and easy to read operations on the response of the promise.

Promise.all()

Promise.all() takes an array of promises and will not fulfil the promise until either all of the promises have resolved or one of them has been rejected.

var promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'I resolved!');
});
// these two promises count as resolved
// because they are just values
var promise2 = resolve(5);
var promise3 = 28;

Promise.all([promise1, promise2, promise3])
    .then((results) => {
        console.log(`All promises complete: ${results}`);
    }).catch((error) => {
        console.error(`One or more promises rejected: ${error}`)
    });

Note: The console statements in this example use template literals which is a really cool way of embedding expressions in string literals.

Promise.all() can be useful if you're sending a bunch of promises at once and need all the information before doing stuff with it.

Promise.race()

Promise.race() allows you to race an array of promises, where the race will resolve or reject as soon as one of the promises in the array is fulfilled.

var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];

var p = Promise.race(resolvedPromisesArray);
console.log(p);

// using setTimeout we can execute code
// after the stack is empty
setTimeout(() => {
    console.log('the stack is now empty');
    console.log(p);
});

In the above example the two promises are already resolved, therefore the first one that is read resolves the race. However because this runs on a single thread, you won't see that the race is resolved until the stack is empty and it's had a chance to run!

I would advise playing around with Promise.race(), so that you really get to grips with it's behaviour. For instance, it will never resolve if you give it an empty array!

Fetch API

The Fetch API is replacing the age old XMLHttpRequest and the Ajax request wrapper. It also implements promises for you and handles CORS!

fetch('/example.json')
    .then(response => response.json())
    .catch(error => console.log(`Fetch failed: ${error}`));

This is a very simple and quick example, but for those who are used to writing out an Ajax or XHR you can instantly see how much code isn't there. What a welcome sight!

However because Fetch does so much for us, we need to be a little more guarded with how we handle our response and catch errors. Fetch will only fail if it cannot reach the server, the promise will resolve even if the response is an errenous code! Another common pitfall are opaque responses, which are responses we cannot examine because they are from another origin.

fetch('https://some-web.site/data.json')
    .then(response => {
        // if the response is opaque we cannot examine it, only serve it
        if (response.type === 'opaque') {
            return response;
        } else if (!response.ok) {
            // response.ok is true for any response
            // of good header! e.g. 200, 201
            throw new Error(response.statusText);
        } else {
            // this block is where you would cache and serve!
            return response;
        }
    }).catch(error => {
        // if you reach this block, it means that the
        // origin you tried to reach is offline
        console.error(error);
        return OFFLINE_404;
    });

In this example we guard against both opaque responses and bad responses, this makes our fetch robust to the most common issues encountered in general use of the Fetch API.


Leave a comment

Let me know how you got on with this tutorial, I hope there was no confusion and that you feel ready to really get into building your own progressive web application now. Drop a comment or tweet me!