lunes, 24 de marzo de 2014

A bit deeper into Promises and Mongoose

On the internet you can find thousands of tutorials about promises and how to use them.
I recommend this webpage http://www.promisejs.org/ if you are starting with promises. I don't pretend to cover promises basics in this post.

Promises are a very simple but I had some problems at the beginning with a real life example. I only found very simple promises examples.

For this example we are going to use the Q library (https://github.com/kriskowal/q). In that page there are many examples on how to use the library.

We're also using Mongoose to save a client to the database.

For this example, the client id must be unique.

This is the client schema (client-model.js):

module.exports = function(app, mongoose){
 
 var Schema = mongoose.Schema;  

 var client = new Schema({
     id: String,
     name: String,
     lastName: String,
     gender: String
 });
 return mongoose.model('Client', client);
};


And this is the function that saves the client. We will validate an empty or repeated ID, empty name or empty lastName (this can be validated by Mongoose with required and unique, but for this example we'll do it manually)



var Q = require('q'),
 mongoose = require('mongoose');

module.exports = function(app) {

 var clientModel =  require('../models/client-model.js')(app, mongoose);

 var isNotEmptyOrNull = function(pValue) {
  if (!pValue) {
   return false;
  } else if (pValue.length < 1) {
   return false;
  }
  return true;
 };

 app.post('/api/client', function(pRequest, pResponse) {

  // Validate empty id, name and lastName
  Q.all([
   isNotEmptyOrNull(pRequest.body.id),
   isNotEmptyOrNull(pRequest.body.name),
   isNotEmptyOrNull(pRequest.body.lastName)
  ])
  .then(function(pResults) {
   // pResults is an array with the result of each function. In this case, we expect it to be [true, true, true]
    
   var findClient = Q.nbind(clientModel.find, clientModel);

   // Check if id, name and lastName is not null or empty
   var isValidObject = pResults.every(function(pResult) {
    return pResult;
   });

   if (!isValidObject) {
    // It's not a valid object, we send a bad request to the user
    throw 'Id, name and lastName are required';
   }

   return findClient({ id: pRequest.body.id});
  })
  .then(function(pFoundClient) {

   // The id already exists
   if (pFoundClient && pFoundClient.length > 0) {
    throw 'Id must be unique';
   }

   // Everything is ok, we save the client
   var clientToSave = new clientModel(pRequest.body);
   return Q.when(clientToSave.save());
  })
  .then(function() {

   return pResponse.send('Client saved');

  })
  .catch(function(pError) {
   // Catch any error
   console.log(pError);
   return pResponse.send(400, pError);
  });
 });

};

This is the explanation on Q.all from the Q API reference:
Returns a promise that is fulfilled with an array containing the fulfillment value of each promise, or is rejected with the same rejection reason as the first promise to be rejected. This method is often used in its static form on arrays of promises, in order to execute a number of operations concurrently and be notified when they all succeed
And this is nbind:
Creates a promise-returning function from a Node.js-style method, optionally binding it with the given variadic arguments.
With that example it should be clear how to use Q Promises and Mongoose to save the object. Please leave questions, comments or feedback. Thanks

3 comentarios:

  1. In the following method:
    var isNotEmptyOrNull = function(pValue) {
    if (pValue == null) {
    return false;
    } else if (pValue.length < 1) {
    return false;
    }
    return true;
    };

    if (pValue == null) will go false with an undefined value in pValue and even this if (pValue.length < 1) will crash because undefined values doesn't have length, it would be simpler to use the truthy and falsey values and just check like this:

    var isNotEmptyOrNull = function(pValue) {
    return (pValue && pValue.length > 0);
    }

    ResponderEliminar
    Respuestas
    1. When pValue is undefined, pValue == null is true.

      You can try it in your browsers console, type this:
      var test = {};
      console.log(test == null); // -> result is false
      console.log(test.innerTest == null) // -> result is true

      Eliminar
  2. var test = {}; //has no undefined value, it just has an empty object
    var test = undefined; // has it

    To make myself clearer, just try this:
    var test = {};
    console.log( test ? "yes" : "no");

    test = undefined;
    console.log( test ? "yes" : "no");

    Is better to just validate like this if(test) than validating by types (null , undefined).

    ResponderEliminar