title: Catching errors with ES6 promises and BookshelfJS
body: <p>I'm adding a very basic login method to a Bookshelf User model in an ExpressJS app, but I can't catch the errors from the rejected promise that the login function in the User model returns. I'm looking at Bookshelf's example login in the docs at <a href="http://bookshelfjs.org/#Model-static-extend" rel="nofollow">http://bookshelfjs.org/#Model-static-extend</a> but that example uses Bluebird, whereas I'm trying to do the same with the built-in ES6 promises.</p>
  
  <p>My login method in the User model:</p>
  
  <p><em>userModel.js</em></p>
  
  <pre><code>function login(email, password) {
    return new Promise((resolve, reject) =&gt; {
      User.where('email', email)
        .fetch({ require: true })
        .then(user =&gt; {
          bcrypt.compare(password, user.get('password'), (err, matched) =&gt; {
            if (!matched) return reject(new Error('Password didn\'t match!'));
            resolve(user);
          });
        });
    });
  </code></pre>
  
  <p>The controller action that implements login and calls <code>User.login</code> from the Bookshelf <code>User</code> model:</p>
  
  <p><em>usersAuthController.js</em></p>
  
  <pre><code>function logUserIn(req, res) {
    new User().login(req.body.email, req.body.password)
      .then(user =&gt; res.json({ message: 'Login succeeded!' }))
      .catch(User.NotFoundError, () =&gt; res.status(404).json({ error: 'User not found!' }) // catch #1
      .catch(err =&gt; res.status(401).json({ err: err.message })); // catch #2
  }
  </code></pre>
  
  <p>My intention is that <code>login()</code> can return a rejected promise when Bookshelf's <a href="http://bookshelfjs.org/#Model-instance-fetch" rel="nofollow"><code>User.fetch</code></a> method can't find the user with the given email. In that case, the <code>.catch(User.NotFoundError ...)</code> (catch #1) line should catch it and return a 404. I also intend <code>login()</code> to return a rejected Promise when <code>bcrypt</code> determines that the password passed to <code>login()</code> doesn't match the user's password, in which case the "catch-all" (catch #2) below the <code>User.NotFoundError</code> catch statement should return a 401.</p>
  
  <p>If I put in the incorrect password, the <code>logUserIn()</code> controller action in the above code goes to catch #2 with the error message <code>{ error: "Cannot set property 'message' of undefined" }</code> instead of the message <code>'Password didn't match!'</code> message that I rejected in <code>login()</code>. If I put in a nonexistent email, the response is never sent and the error <code>Unhandled rejection CustomError: EmptyResponse</code> is thrown in the console. Only valid input works.</p>
  
  <p><strong>An attempt at fix:</strong> catching <code>User.NotFoundError</code> directly in the model instead.</p>
  
  <p>I moved catch #1 to the User model so that the login method now looks like:</p>
  
  <p><em>userModel.js</em></p>
  
  <pre><code>function login(email, password) {
    return new Promise((resolve, reject) =&gt; {
      User.where('email', email)
        .fetch({ require: true })
        .then(user =&gt; {
          bcrypt.compare(password, user.get('password'), (err, matched) =&gt; {
            if (!matched) return reject(new Error('Password didn\'t match!'));
            resolve(user);
          });
        })
        .catch(User.NotFoundError, () =&gt; reject({ error: 'User not found!' }));
    });
  </code></pre>
  
  <p>This way, I can catch both errors (incorrect password and nonexistent email) correctly, but this way I can't specify the status code in the controller. If the user with the given email couldn't be found, then it should return a 404, but if the password was incorrect, then it should return a 401, but both errors go to the catch-all (catch #2) in the controller action (which always returns a 401).</p>
  
  <p>To solve this problem, in the <code>User</code> model I could do <code>.catch(User.NotFoundError, () =&gt; reject({ name: 'NotFoundError', message: 'User not found!' }))</code> and in the controller action I can check for what kind of error I'm getting with <code>const statusCode = err.name === 'NotFoundError' ? 404 : 401</code> but that seems really messy and misses the point of having these <code>.catch</code> statements.</p>
  
  <p>Is there a way to catch the <code>User.NotFoundError</code> from the model's login method and any other errors all in <code>logInUser</code>? Why doesn't the setup I had at first work, the one with both <code>catch</code> statements in usersAuthController.js, and what did the <code>Cannot set property 'message' of undefined'</code> and <code>CustomError: EmptyResponse</code> errors mean (does it have something to do with mixing up Bookshelf's Bluebird promises versus the built-in ES6 promises)? What's the best way to handle this?</p>