0

Edit 2018-01-18: Use async/await instead of promise chaining. It will solve all your problems.

I have this code for mongoose in NodeJS

    User.find({ name: 'John' })
      .then((users) => {
        if (!users.length) return res.send('No users found.');
        return Request.find({ cost: 100 })
      })
      .then((requests) => {
        console.log('should not get here');
        return res.json(requests);
      })
      .catch((err) => {
        res.status(500).json(err);
      })

I wish to stop the execution if there are no users found, and just send "No users found." without executing anything else.

I am aware I could use throw res.send('No users found.'); instead.

but then that would make it impossible for me to catch genuine errors - that might happen while saving or updating for example - and manage them.

How should I approach this? Should I use a different code structure? I like how simple and maintainable this structure is, except for this one downside.

Hafez
  • 1,320
  • 2
  • 13
  • 24
  • *"but then that would make it impossible for me to catch genuine errors"* How so? Because the last time I checked the driver returned "informative" structured error objects with error codes. A regular pattern is "ingnoring/warning" on "duplicate key errors", which whilst an exception is not really fatal, like a lost database connection of something else actually severe. So the typical thing to to is "inspect" within your `.catch()`, and then decide what to do with `err` based on what information it has. So `throw new Error()` with something to identify it **IS** the right thing to do. – Neil Lunn Nov 02 '17 at 00:34
  • There are many reasons that could cause an error, a mongoose error, a mongodb error. What unified structure do you propose I should use? I just did ```if ( !err.domain ) { //this is a genuine error }``` That way I am excluding anything that is not a ```res.send()``` Is there a better way to recognize errors? – Hafez Nov 02 '17 at 00:43
  • There is no such thing as a "mongoose error". This is "straight through to the driver". I just gave you a nice little packaged example of what the rest of the world does. Create some deliberate write errors yourself and inspect the objects returned and then you will see exactly what they are like. You need to do the same thing. As the example I gave, a **regular** practice is expecting duplicate key `E11000` by looking at `err.code` as a property on the returned error object. – Neil Lunn Nov 02 '17 at 00:47
  • 1
    You need to do the same sort of thing. "If code is this, then this action else other action, otherwise general error report. This is simple stuff. You will in fact find many, many examples of exactly what I am saying here in any reputable code base. Try and see and learn. – Neil Lunn Nov 02 '17 at 00:48
  • Thanks a lot, I thought that was a bit of a hack, but since it's widely used, maybe it's not that bad a practice, I will use it. Feel free to write an answer to this and I will mark it as the right answer. – Hafez Nov 02 '17 at 00:50
  • 1
    Even more common is ["duck typing"](https://en.wikipedia.org/wiki/Duck_typing) by which you can further abstract on "is a mongo error", or is a "not found error". Now you have something to research for the day. – Neil Lunn Nov 02 '17 at 01:01

1 Answers1

1

You can extend Error classes in ES2015/6

class ExtendedError {
  constructor(message){
    super(message)
    this.name = this.constructor.name
    this.message = message
    if (typeof Error.captureStackTrace === 'function'){
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class NotFoundError extends ExtendedError {
   constructor(message, options){
     super(message)
     this.status = 404
     this.code = 'NF0001'
   }
}

Your error handling code can then look at more metadata in the error to make it's decision about how to response.

User.find({ name: 'John' })
  .then((users) => {
    if (!users.length) throw new NotFoundError('No users found.')        
    return Request.find({ cost: 100 })
  })
  .then((requests) => {
    console.log('should not get here')
    return res.json(requests);
  })
  .catch((err) => {
    let status = err.status || 500
    res.status(500).json({ error: err })
  })

You probably want to create a generic express API request/response handler so you are not repeating the response and error handling for every handler. Then the handler code only needs to return data, or throw errors.

User.find({ name: 'John' })
  .then((users) => {
    if (!users.length) throw new NotFoundError('No users found.')        
    return Request.find({ cost: 100 })
  })
Matt
  • 68,711
  • 7
  • 155
  • 158