Javascript ES6 Promises : basic example

Christian Nadeau
5 min readFeb 10, 2017

--

We just started a new project and we realized that not everyone was familiar with the Javascript ES6 Promises. This concept helps to get code readable and very scoped, but, it can go messy if you don’t get it properly.

Note: Reference material can be found here

Example functions

Here are 2 functions we will use to control the behaviour of a then or catch clause

// Outputs it is currently within a then, and allows to throw
// a new error easily right after
function handleThen(id) {
console.log(`then ${id}`);
// Returns an object with throwError function
// to be able to throw a new error in a then
return {
throwError: () => {
throw new Error(`error ${id}`);
},
reject: () => Promise.reject(new Error(`error ${id}`))
};
}
// Outputs it is currently within a catch, and allows to throw
// a new error easily right after
function handleCatch(id, error) {
console.log(`catch ${id} : ${error}`);
// Returns an object with rethrow function
// to be able to rethrow the received error
// in a catch
return {
rethrow: () => {
throw error;
},
reject: () => Promise.reject(error)
};
}

Basic Flow

Promise.resolve()
.then(() => handleThen(1))
.catch(e => handleCatch(1, e))
.then(() => handleThen(2))
.catch(e => handleCatch(2, e))
.then(() => handleThen(3))
.catch(e => handleCatch(3, e))
.catch(e => console.log(`final catch : ${e}`))
.then(() => console.log('Done'));

NOTE: We start with Promise.resolve() in this example just to start a Promise chain.

Result:

then 1
then 2
then 3
Done

No error caught because there weren’t any.

What if an exception occurs in the first then?

Promise.resolve()
.then(() => handleThen(1).throwError())
.catch(e => handleCatch(1, e))
.then(() => handleThen(2))
.catch(e => handleCatch(2, e))
.then(() => handleThen(3))
.catch(e => handleCatch(3, e))
.catch(e => console.log(`final catch : ${e}`))
.then(() => console.log('Done'));

Result:

then 1
catch 1 : Error: error 1
then 2
then 3
Done

Hey wait! There was an Error, why does the execution goes on? Maybe I should use Promise.reject instead:

Promise.resolve()
.then(() => handleThen(1).reject())
.catch(e => handleCatch(1, e))
.then(() => handleThen(2))
.catch(e => handleCatch(2, e))
.then(() => handleThen(3))
.catch(e => handleCatch(3, e))
.catch(e => console.log(`final catch : ${e}`))
.then(() => console.log('Done'));

Result:

then 1
catch 1 : Error: error 1
then 2
then 3
Done

Hum… same problem… Let’s have a look at the documentation. As you can see in the flow diagram

https://mdn.mozillademos.org/files/8633/promises.png

rejection DOES NOT stop or break the Promise chain. It returns a Promise!

Then, how to avoid execution flow to continue?

The goal is to stop the execution of the actual Promise and ensure any following clauses other than the next catch is skipped/ignored.

To do so, you must ensure the catch clause itself throws or returns a rejected Promise.

Throw an Error

Throwing an error will break the execution and ensure the next available catch clause is reached.

Use Promise.reject

Promise.reject is a helper function that will return a rejected Promise, leading to the next catch clause.

Promise.resolve()
.then(() => handleThen(1).reject())
.catch(e => handleCatch(1, e).rethrow())
.then(() => handleThen(2))
.catch(e => handleCatch(2, e).rethrow())
.then(() => handleThen(3))
.catch(e => handleCatch(3, e))
.then(() => console.log('Done'));

Result:

then 1
catch 1 : Error: error 1
catch 2 : Error: error 1
catch 3 : Error: error 1
Done

Same thing results using Promise.reject in the catch clause:

Promise.resolve()
.then(() => handleThen(1).reject())
.catch(e => handleCatch(1, e).reject())
.then(() => handleThen(2))
.catch(e => handleCatch(2, e).reject())
.then(() => handleThen(3))
.catch(e => handleCatch(3, e))
.then(() => console.log('Done'));

Result:

then 1
catch 1 : Error: error 1
catch 2 : Error: error 1
catch 3 : Error: error 1
Done

UnhandledPromiseRejectionWarning…

If you forget to properly handle the Promise chain rejection by adding a final catch that does the global error handling as follow:

Promise.resolve()
.then(() => handleThen(1).reject())
.catch(e => handleCatch(1, e).reject())
.then(() => handleThen(2))
.catch(e => handleCatch(2, e).reject())
.then(() => handleThen(3))
.catch(e => handleCatch(3, e).reject())
.then(() => console.log('Done'));

it will lead you to this result:

then 1
catch 1 : Error: error 1
catch 2 : Error: error 1
catch 3 : Error: error 1
(node:16972) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 5): Error: error 1
(node:16972) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

To avoid this, you MUST add a final catch somewhere in your code that handles any global error and DOES NOT rethrow or throw any error with the chain you are executing

Promise.resolve()
.then(() => handleThen(1).reject())
.catch(e => handleCatch(1, e).reject())
.then(() => handleThen(2))
.catch(e => handleCatch(2, e).reject())
.then(() => handleThen(3))
.catch(e => handleCatch(3, e).reject())
.catch(e => console.log(`something went wrong : ${e}`))
.then(() => console.log('Done'));

Result:

then 1
catch 1 : Error: error 1
catch 2 : Error: error 1
catch 3 : Error: error 1
something went wrong : Error: error 1
Done

A more meaning full example

Let’s suppose you want to perform an authenticated API call to fetch some data. You might end up doing something like

function login(){
...login...
}
function fetchData(){
...fetches data...
}
function fullFlow(cb) {
login((err) => {
if (err) {
return cb({ error: err, code: 401 });
}
// Login successful fetchData((er, res) => {
if (er) {
return cb({ error: er, code: 500 });
}
// Fetch data successful processResult(res, (e, r) => {
if (e) {
return cb({ error: e, code: 500 });
}
return cb(null, r);
});
});
});
}

You have a lot of cases to handle and the code is really not clear.

Now what if api returned a Promise instead

function login(){
...returns login Promise...
}
function fetchData(cb){
...returns fetch data Promise...
}
login()
.catch(e => Promise.reject(e))
.then(() => fetchData())
.then(result => processResult(result))
.then(finalResult => console.log(finalResult))
.catch(e => {
console.log(`An error occurred : ${e}`);
});

Login fails (return Promise.reject(‘Unauthorized’)):

An error occurred : Unauthorized

FetchData fails (return Promise.reject(‘Unable to fetch data’))

An error occurred : Unable to fetch data

ProcessResult fails

An error occurred : Processing error

Everything was fine:

Done

Unit Testing

A plus to this is it even simplifies a lot unit testing each function using libraries such as chai-as-promised

const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
const demoPromise = require('./demo-promise');describe('login', () => {
it('should fail', () => {
return expect(demoPromise.login(false)).to.have.been.rejected
.then(res => {
expect(res).to.be.eql('Unauthorized');
});
});
it('should succeed', () => {
return expect(demoPromise.login()).to.have.been.fulfilled
.then(res => {
expect(res).to.be.eql('Success');
});
});
});

Conclusion

Promise allows you to structure the code in a way it’s readable, easy to maintain and unit testable. Give it a shot! ;-)

--

--