The dos and don'ts of Promises

31-03-2018 • Javascript Patterns

Last week I came across an old nodejs project. It was the first project in which I had ever used promises and I made every mistake one can make. Luckily I'm a lot wiser now, so I decided to write this post to share a few tips on how to properly use promises.

Returning a promise

A common mistake is to forget that the asynchronous function you are using already returns a Promise and instead of returning this existing promise you create a new one. The solution is quite simple, we return the promise returned by the asynchronous function.

Don't

function myFunction() {
  return new Promise((resolve, reject) => {
    asyncFunction()
    .then(result => {
      resolve(result)
    })
    .catch(err => {
      reject(err)
    })
  })
}

Do

function myFunction() {
  return asyncFunction()
}

Returning a value

When you need to return a value, you also don't need to create a new promise. The then() method al ready returns a promise, returning a value from within the then() method will resolve this promise with that value.

Don't

function myFunction() {
  return new Promise((resolve, reject) => {
    asyncFunction()
    .then(result => {
      const computedResult = computeResult(result)
      resolve(computedResult)
    })
  })
}

Do

function myFunction() {
  return asyncFunction()
  .then(result => {
    const computedResult = computeResult(result)
    return computedResult
  })
}

Promisify a function or method

Many times you might want or need to convert an asynchronous function with a callback to an asynchronous function that returns a promise. Many libraries or environments (like bluebird or nodejs) have special functions for this exact purpose, use those functions.

Don't

function myFunction() {
  return new Promise((resolve, reject) => {
    asyncFunctionWithCallback((err, result) => {
      if (err) reject(err)
      resolve(result)
    })
  })
}

Do

// nodejs example, promisify a function
const { promisify } = require('util')

const functionWithPromise = promisify(asyncFunctionWithCallback)

function myFunction() {
  return functionWithPromise()
}
// nodejs example, promisify a method
const { promisify } = require('util')

const someObject = new ObjectWithMethods()
const methodWithPromise = promisify(someObject.method).bind(someObject)

function myFunction() {
  return methodWithPromise()
}

Collections

You have an array or object containing multiple items and you want to call an asynchronous function for each of them. To keep the code tidy and readable you can use the Array.prototype.map() method.

Don't

function myFunction(items) {
  const promises = []
  items.forEach(item => {
    promises.push(asyncFunction(item))
  })
  return Promise.all(promises)
}

Do

// array example
function myFunction(items) {
  return Promise.all(items.map(item => {
    asyncFunction(item)
  }))
}
// object example
function myFunction(items) {
  return Promise.all(Object.keys(items).map(key => {
    asyncFunction(items[key])
  }))
}

Avoid breaking the promise chain

Always chain your promises together with the then() method. If we break this chain, errors might not be caught properly.

Don't

function myFunction() {
  const promise = asyncFunction()
  promise.then(result => {
    return computeResult(result)
  })
  // here the chain is broken
  return promise
}

myFunction()
.catch(err => {
  // will not catch errors inside asyncFunction()
  console.log(err)
})

Do

function myFunction() {
  const promise = asyncFunction()
  return promise
  .then(result => {
    return computeResult(result)
  })
}

myFunction()
.catch(err => {
  console.log(err)
})
// or even shorter
function myFunction() {
  return asyncFunction()
  .then(result => {
    return computeResult(result)
  })
}

myFunction()
.catch(err => {
  console.log(err)
})