简体   繁体   中英

NodeJS best practices: Errors for flow control?

In Node.js, should I use errors for flow control, or should I use them more like exceptions?

I'm writing an authentication controller and some unit tests in Sails.js, and currently, my registration method checks to see if a user with the same username exists. If a user already exists with the username, my model method calls its callback arguments with a new Error object, like so:

Model:

exists: function (options, cb) {
    User.findOne({
        where: { username: typeof options === 'Object' && options.username ? options.username : options },
    }).exec(function (err, user) {
        if (err) return cb(err);
        if (user) return cb(new Error("A user with that username already exists."));
        cb(null, !!user);
    });
},

Controller:

User.exists(req.body.user.username, function (err, exists) {
  if (err) {
    console.log("error: ", err);
    return res.status(409).json({
      message: err
    });      
  }

  User.create(req.user).then(function (data) {
    res.status(201).json({
      user: data
    });
  });
});

Is this best practice? I'm not sure if node conventions favor errors for exceptional cases, or for flow control. I'm thinking I should rewrite this, but I want to know conventions before I do so. I think I've seen some examples written this way in Sails. Thanks!

Node (or Javascript really) can throw errors on exceptions by using the keyword throw :

if (something_went_wrong) {
  throw new Error('Doh!');
}

You can also add additional parameters to the default Error object to give more semantic meaning to your errors in your program. With that said, you wouldn't want to throw an error in your route handler because this would crash the process and your server.

When using route handlers in Sails (or express really), you most certainly should check the type of error and respond to the client accordingly.

// -- Route handler
app.get('/something', function (req, res, next) {

  DB.create({ username: 'user' }, function (err, docs) {

    if (err) {

      // This checks to see if we have a unique error from MongoDB
      // and send the appropriate response to the client
      if (err.code === 11000 || error.code === 11001) {
        return res.send(409); // or return res.json(409, {message: "User exists"});
      }

      // Any other error type is assumed to be an internal error so we pass it
      // down the chain to the error handler middleware
      return next(err);

    }

    // This is a client error, not an internal error so we respond to the client
    // with the appropriate client error type
    if (docs.length === 0) return res.send(404);

    if (user.notAuthorized) return res.send(403);

    res.send('All Good');

  });

});

Notice that in the case that the DB responds with an internal error, we pass to the next() function which is picked up by error handling middleware down the chain. Any middleware with an arrity of 4 is defined as error handling middleware. Sails probably has some default error handler, but you can probably also override it — you'll need to check the appropriate docs for this information as I prefer the control I gain by using Express alone.

The above answer is good for Express, but in Sails controllers you should not be calling next ; best practice is to always return a response. In most example Sails code you won't even see next as an argument to a controller action function. Also note that Sails comes with some default response methods built right into the res object, such as res.serverError and res.badRequest , as well as res.negotiate which will attempt to route the error to the appropriate handler for you based on the status code. So your example could be tweaked as:

Model:

exists: function (options, cb) {
    User.findOne({
        where: { username: typeof options === 'Object' && options.username ? options.username : options },
    }).exec(function (err, user) {
        // res.negotiate will default to a 500 server error
        if (err) return cb(err);
        // res.negotiate will just output the status code and error object
        // as JSON for codes between 400 and 500, unless you 
        // provide a custom view as api/responses/badRequest.ejs
        if (user) return cb({
          status: 409, 
          message: "A user with that username already exists."
        });
        cb(null, !!user);
    });
},

Controller:

User.exists(req.body.user.username, function (err, exists) {
  // Let Sails handle those errors for you
  if (err) {return res.negotiate(err);}

  User.create(req.user).then(function (data) {
    res.status(201).json({
      user: data
    });
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM