When writing promise .catch() blocks, what's the difference between e and e.message?

Viewed 45

I'm pretty new to promises, and came across something I was wondering about. Here is some simple fetch code, and in the .catch() block, we use e.message to log the "human-readable description of the error".

function fetchAndDecode(url, type) {
  return fetch(url).then(response => {
    if(!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    } else {
      if(type === 'blob') {
        return response.blob();
      } else if(type === 'text') {
        return response.text();
      }
    }
  })
  .catch(e => {
    console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
  })

Then, here's a user-made promise using the Promise() constructor as opposed to the fetch API, and in the .catch() block, e.message is undefined, and instead we have to use e.

function timeoutPromise(message, interval) {
  return new Promise((resolve, reject) => {
    if (message === '' || typeof message !== 'string') {
      reject('Message is empty or not a string');
    } else if (interval < 0 || typeof interval !== 'number') {
      reject('Interval is negative or not a number');
    } else {
      setTimeout(function(){
        resolve(message);
      }, interval);
    }
  });
};

timeoutPromise('Hello there!', 1000)
.then(message => {
   alert(message);
})
.catch(e => {
  console.log('Error: ' + e); // e.message doesn't work here (it's undefined)
});

What is the reason for this? Do you simply use e.message with fetch and e for promises made using the Promise() constructor? Is it some other reason I'm overlooking?

2 Answers

It has to do with the autonomy of an error object in Nodejs. The error object is a built-in object that provides a standard set of useful information when an error occurs.

The error constructor creates an error object:

new Error([message[, fileName[, lineNumber]]])

An error object is composed of:

error.code // The error code
error.message // The error message
error.stack //shows you where an error came from

How an error object is created:

const error = new Error("The error message");
console.log(error);
console.log(errpr.message)
console.log(error.stack);

So in a try/catch block:

try {
    // Do stuff here
} catch (e) {
    console.log(e) // You are logging the entire error object
    console.log(e.message) // You are logging the error message, which is a property of the error object.
}

In the user-made promise:

Case 1: Promise rejection is returning a string

function timeoutPromise(message, interval) {
    return new Promise((resolve, reject) => {
        if (message === '' || typeof message !== 'string') {
            reject('Message is empty or not a string');
        } else if (interval < 0 || typeof interval !== 'number') {
            reject('Interval is negative or not a number');
        } else {
            setTimeout(function () {
                resolve(message);
            }, interval);
        }
    });
}

timeoutPromise(1, 1000)
    .then(message => {
        console.log(message);
    })
    .catch(e => {
        console.log('Error: ' + typeof (e)); 
     // e is not an error object, it is a string
     // So e.message is udefined
    });

Case 2: Promise rejection is returning an error object


function timeoutPromise(message, interval) {
    return new Promise((resolve, reject) => {
        if (message === '' || typeof message !== 'string') {

            // The first parameter passed is the error.message
            // Look at the syntax of the error constructor above
            reject(new Error('Message is empty or not a string'));

        } else if (interval < 0 || typeof interval !== 'number') {

            // The first parameter passed is the error.message
            // Look at the syntax of the error constructor above
            reject(new Error('Interval is negative or not a number'));

        } else {
            setTimeout(function () {
                resolve(message);
            }, interval);
        }
    });
}

timeoutPromise(1, 1000)
    .then(message => {
        console.log(message);
    })
    .catch(e => {
        console.log('Error: ' + typeof (e)); 
       // e is an error object, and e.message is available
    });

The first argument .catch receives is whatever the creator of the above Promise decided to reject (or throw) with.

In the first case, an error is being thrown:

throw new Error(`HTTP error! status: ${response.status}`);

This means that the e below in the catch will be the exact same expression as what's on the right of the throw:

new Error(`HTTP error! status: ${response.status}`);

And Error objects have a message property:

const err = new Error(`HTTP error! status: 500`);

console.log(err.message);

In contrast, in the second code, reject (which does the same thing as throw in this situation) is not called with an Error instance, but with a a plain string:

reject('Message is empty or not a string');
reject('Interval is negative or not a number');

These plain strings can be logged just by logging them or concatenating them:

const e = 'Message is empty or not a string';
console.log('Error: ' + e); 

Whenever you have a .catch, to decide whether to log the whole argument or access a .message property or something else, look at the creator of the Promise to see what the rejection value will be. It will vary.

Sometimes you might not have any idea of what it'll reject with, in which case a common enough pattern is to check if it's an Error and log the message, and otherwise, to just log it:

const handleError = (e) => {
  if (e instanceof Error) {
    console.error(e.message);
  } else {
    console.error(e);
  }
};
Related