Thursday, October 2, 2014

Variable Passing with Promises

Hey guys, just figured out a trick with promises that makes the code look cleaner, is more readable, and just plain sweet!

Problem:
function recipeForSuccess(){
  return barAsync()
   .then(function(barData){
       return fooAsync(barData);
   })
   .then(function(fooData){

       // getFooBar requires fooData and myBar
       // but I only have access to fooData within this functions scope
       
       return getFooBar(fooData, myBar);
   })
   .catch(function (err) {
      console.log(err); 
   });
}

Solutions:

The "Old" Way:
Whenever you needed access to data down the promise chain you tied it to a variable scoped outside of your promise chain.

Example:

function recipeForSuccess(){
  var myBar; // will be defined when the barAsync promise is resolved
  return barAsync()
   .then(function(barData){
       myBar = barData;  // defining myBar for use further down the promise chain.
       return fooAsync(barData);
   })
   .then(function(fooData){
       // now I have access to the data returned from barAsync by using myBar.
       return getFooBar(fooData, myBar);
   })
   .catch(function (err) {
      console.log(err); 
   });
}

Downsides: This clutters the code up and when you read it from top to bottom you have to bounce around to figure out where myFoo is coming from / being defined.

The "New" Way:
Instead of declaring myFoo at the beginning and then assigning data to it half way through the Promise chain, why don't we just pass it to the next Promise in the chain? This improves the code by allowing the code to flow from top to bottom only and removes unnecessary variable declarations that clutter up the code.


function recipeForSuccess(){
  return barAsync()
   .then(function(barData){
       return Q.all([
          fooAsync(barData),
          barData
       ]);
   })
   .then(function(data){
       // data[0] will contain whatever is returned by barAsync(fooData);
       // data[1] will contain fooData
       return getFooBar(data[0], data[1]);
   })
   .catch(function (err) {
      console.log(err); 
   });
}

You can also write it like this:
function recipeForSuccess(){
  return barAsync()
   .then(function(barData){
       return Q.all([
          fooAsync(barData),
          barData
       ]);
   })
   .spread(function(fooData, barData){
       return getFooBar(fooData, barData);
   })
   .catch(function (err) {
      console.log(err); 
   });
}

The Importance of Cards and Pull Requests

We move pretty fast compared to most development groups.  A small team with lots of communication means moving fast isn't too dangerous.  However, as we bring in more people and projects, that's going to get a lot harder.

As developers, there are a few simple things you can do that won't take much time, but will go a long way toward preventing critical issues and bugs in production.

Whenever you work on a new feature, enhancement, or bug fix, make sure there is a card on the project's Trello board that describes the work you will be doing.  Add as much detail as makes sense without being too verbose.

Once you are sure you are working from a card, create a new branch for the changes in the card.  It's important that you have a card for each change, and a branch for each card.  It may not always be possible or practical to work this way, but those should be rare exceptions.

When you are finished with the branch and create a pull request to merge back to master, your pull request should only contain changes for that one card.  That way it's easy to review and test pull requests and have a fairly good idea of what in the project is being affected.

This keeps each card on the fastest track to production.  If multiple cards are combined in a pull request, and something needs to get to production fast, things will slow down as we untangle the implications of multiple changes in a branch and make sure everything is tested correctly.

Once your pull request is submitted, make sure you have a conversation with your code reviewer and your DevOps engineer.  The conversation should cover what the implications of the change are, what else could be affected, what testing have you done on your own code, and what else may need to be tested.  This conversation doesn't need to be longer than five minutes, unless your change is really large or complex.

One of the keys to moving fast is keeping things simple.  A good way to keep things simple is one change per card - one card per branch/pull request - and a release conversation before every pull request gets pushed to production.

User Experience Resources

I just wanted to throw out some great resources for anyone interested in User Experience.  The concepts here are valuable to just about any software developer.  Of course this list will get stale over time.

Regular articles


Printed Resources



Statistics




Resource for resources