#100DaysOfMERN - Day 11

#100DaysOfMERN - Day 11

·

8 min read

On Day 9, I started learning about network requests and wrote a simple function using XMLHttpRequest and a callback, to fetch data from an API. Today, I'll refactor the code to use JavaScript Promises instead, which should later help to understand what the fetch() method does under the hood.


✏ Rewriting the XMLHttpRequest

Starting with the (slightly modified) original function:

const usersUrl = 'https://jsonplaceholder.typicode.com/users';

function getData(url){
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.onload = () => {
        if (xhr.status === 200){
            displayUsers(JSON.parse(xhr.response))
        } else if (xhr.status >= 400){
            console.log(`ERROR: ${xhr.status}`)
        }
    }
    xhr.onerror = () => console.log(`ERROR: ${xhr.status}`)
    xhr.send();
}

getData(usersUrl)

That displayUsers function isn't relevant, it only loops over the returned array of user objects and creates a number of <li> tags, to show the names on the page. You could as well just log the data, but I preferred to make it a bit more realistic.


Creating the Promise

First step of refactoring: My getData function should return a Promise. In the case of a successful request, I want to call resolve, if an error occurs, I want to call reject:

const usersUrl = 'https://jsonplaceholder.typicode.com/users';

function getData(url){
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.onload = () => xhr.status === 200 ?
            resolve(JSON.parse(xhr.response)) :
            reject(xhr.status);
        xhr.onerror = () => reject(xhr.status)
        xhr.send();
    })
}

The logic is basically the same as before, the advantages aren't really obvious unless I also call the function and process it with Promise.then():


Consuming the Promise

In the first case, I simply called getData(usersUrl). Although the code worked, it's kind of ugly that the function that makes the request is also responsible for inserting the data into the DOM. With my new Promise-based approach, this will be separated out, and the code will become more readable in terms of "what happens when":

const successUsers = data => displayUsers(data);
const failureUsers = error => console.log(`error getting users: ${error}`);

getData(usersUrl)
    .then(successUsers, failureUsers)

But this is not the only advantage. Imagine I'd have more requests - first, I get a list of users, and once that's completed, I'd like to add another request to fetch a list of blog posts belonging to the users. With the promise-less approach, that would be one more level of nesting callbacks, but with Promises, I only have to chain another .then:

const usersUrl = 'https://jsonplaceholder.typicode.com/users';
const postsUrl = 'https://jsonplaceholder.typicode.com/posts';

const successUsers = data => displayUsers(data);
const failureUsers = error => console.log(`error getting users: ${error}`);

const successPosts = data => displayPosts(data);
const failurePosts = error => console.log(`error getting posts: ${error}`);


getData(usersUrl)
    .then(successUsers, failureUsers)
    .then(() => getData(postsUrl))
    .then(successPosts, failurePosts)

Catching errors

Note that in the above example, I'm providing a failure callback for each request. If the error occured in the first request, I'll get a report that something went wrong when fetching the users data, because failureUsers will be called. The promise chain won't be broken in this case, it will continue with the second request and try to fetch the posts data.

You could rewrite the code and leave out the individual failure callbacks, and add a .catch at the end of the chain instead:

getData(usersUrl)
    .then(successUsers)
    .then(() => getData(postsUrl))
    .then(successPosts)
    .catch(err => console.log(`error: ${err}`))

The error will in this case come from the reject method in the network request function getData. In order to find out which request failed, you could add the xhr.responseURL to the error message. The difference is that if the error occured in the first request (getting the users), the promise chain won't continue, and won't try to carry out the second request (getting the posts).


I suppose it depends on your specific use case which way you prefer. If the second request doesn't make sense in your application unless the first was successful, you'd probably add a .catch at the end of the chain. If the data you fetch in both requests is unrelated, you'd prefer to still try the second request, even if the first failed.

There's a lot more to learn about how to set up a chain of Promises (and there's a few more methods available that I haven't covered yet), so that'll be next on the list.


✏ Recap

I've learned

  • how to fetch data with a function using XMLHttpRequest that returns a Promise instead of running a callback
  • how to set up a short Promise chain
  • individual failure callbacks vs. chaining .catch at the end

✏ Next:

  • more about the Promise chain and additional Promise methods

✏ Thanks for reading!

I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subsribe to my newsletter. Until next time 👋


✏ Previous Posts

  • Day 1: Introduction, Node.js, Node.js in the terminal
  • Day 2: npm, node_modules, package.json and package-lock.json, local vs global installation of packages
  • Day 3: Create a React app without create-react-app, Webpack, Babel
  • Day 4: npx and cowsay
  • Day 5: npm vs. npx, npm audit, semantic versioning and update rules
  • Day 6: Call stack, event loop, JavaScript engine, JavaScript runtime
  • Day 7: Call stack and event loop in Node.js, setImmediate()
  • Day 8: setImmediate(), process.nextTick(), event loop phases in Node.js
  • Day 9: Network requests with XMLHttpRequest and callbacks
  • Day 10: Promises