protips

Logo

This site lists the protips that we shared with students during our courses

View the Project on GitHub appliedtechnology/protips

And .then what - an examination of promises

Promises… it’s a lovely way to get out of callbacks but still not all the way to the async/await bliss that we all want.

However promises are very useful… so let’s learn about one of the parts that tripped me up. It has to do with how we can

Let’s play around with reading some files…

const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);

readFile('index.js')
  .then(data => console.log(data.toString()));

This simply just reads and barfs out the index.js to the console. Notice how I have used util.promisify to create a promise of out the normal callback version of fs.readFile

And, as you have probably heard, a promise can then be in one of three states:

Let’s try to see what happens with these states in our code. We’ll do the weirdest (that I honestly never have cared about in real life) - pending. How can we see that?

const thePromise = readFile('index.js')
  .then(data => console.log('Read it'));

console.log(thePromise);

Pending

This will print Promise { <pending> } to the console. Why? Well - the promise has not completed (resolved) yet. It might be clearer to see in action if we add two more console.log-statements

console.log('Before creating the promise')
const thePromise = readFile('index.js')
  .then(data => console.log('Read it'));
console.log('After creating the promise')
console.log('The status of the promise: ', thePromise);

Yes - the promise is not done (aka resolved), when we get to console.log('The status of the promise: ', thePromise);. It will resolve later. Hold on.

Resolve

Speaking of … what happens when the promise resolve? It calls the .then callback function. That is what happens when you resolve a promise.

readFile('index.js')
  .then(data => console.log('The promise is resolved'));

In fact, let’s write our own promise, rather using the util.promisify(fs.readFile), just to prove a point:

const readData = (file) => {
  new Promise((resolve, reject) => {
    fs.readFile(file, (err, fileData) => {
      if (err) { reject(err); }
      resolve(newArray);
  });
});

When we define a new promise we need to supply a callback function, that takes two parameters; resolve and reject. These two parameters are in fact functions that we use to indicate how the status of the promise is. To resolve the promise… call the resolve.

Rejected

And to reject the promise (aka fail) - just call the reject function and you will end up in the .catch - like this:

readFile('thisFileDoesntExistsAtAll.js')
  .then(data => console.log('The promise is resolved')).
  .catch(err => console.log('The promise is rejected')).

Then, then, then … always then

Now we get to the purpose and whole idea of this blog post. The .then-chaining capabilities.

What if I wanted to read another file after I read the first one, as we did in the promisingInstructors lab?

Well we can do this:

readFile('index.js')
  .then(dataIndex1 => {
    console.log('I have read Index.js')
    return readFile('index2.js');
  })
  .then(dataIndex2 => {
    console.log('I have read Index2.js')
  });

Given that both index.js and index2.js exists this will now run and produce:

› node index.js
I have read Index.js
I have read Index2.js

Since I readFile('index2.js') in my first .then I now have another promise to handle. It might resolve (ending up in the next .then) or reject (ending up in the next .catch).

But it’s actually better than that. WHATEVER I return from inside a .then will be wrapped in a promise and hence be thenable. Look:

readFile('index.js')
  .then(dataIndex1 => {
    console.log('I have read Index.js')
    return 1;
  })
  .then(value => {
    console.log(value);
    return "Marcus";
  })
  .then(value => {
    console.log(value);
    return {name: "Marcus"};
  })
  .then(value => {
    console.log(value);
    return {name: "Marcus"};
  })
  .catch(err => console.log('A promise is rejected', err));

Pretty cool right. Everything that we RETURN from a .then will be treated as a promise and hence end up in the next .then when it resolves. A value, like return "Marcus" will immediately resolve.

So how do I reject from a .then - throw an error:

readFile('index.js')
  .then(dataIndex1 => {
    console.log('I have read Index.js')
    throw new Error("GET ME OUT");
  })
  .then(value => {
    console.log(value);
    return {name: "Marcus"};
  })
  .catch(err => console.log('A promise is rejected', err));

The throw new Error("GET ME OUT") will reject the promise and we will end up in the .catch even though there are more .thens to run.

Single line .then

Notice that if you have single line in your .then you will automatically do a return. If you have more than one line you will have to be explicit with the return. Just like normal for arrow functions - you alway have to return something; you can be explicit or implicit (through single line).

Summary

Understanding Promises can be a beast, but once you wrap your head around a few simple ground rules it becomes clearer:

I hope you found this useful.