This site lists the protips that we shared with students during our courses
.then
what - an examination of promisesPromises… 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:
pending
- when the promise is executing, meaning that we are reading the fileresolved
- meaning that the promise is executed and succeededrejected
- which means that the promise failed for what ever reason.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);
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.
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
.
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')).
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 .then
s to run.
.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).
Understanding Promises can be a beast, but once you wrap your head around a few simple ground rules it becomes clearer:
pending
, resolved
or rejected
statesresolve
you end up in the .then
function on the promisereject
you end up in the .catch
function on the promise.then
functions since everything that you return out of a promise will be wrapped in a promise.I hope you found this useful.