TIL Thursday: Five Crucial Underscore.js Functions

While Underscore comes up in a lot of code and advice I write here on DevSC, it's nice to sometimes to sit back and take a look at the library itself.

The best way to describe Underscore is like a utility belt, one that provides us with essential tools that let us write shorthanded, semantic scripts for functions we use over and over.

You'll be familiar with a lot of functions already. The most obvious and frequently used method being _.extend(). One of the core principles of developing on SCA is that rather than creating new objects, you extend existing ones where possible. We use this extensively throughout Backbone, extending core versions of models, views, routers, etc, on a per-module basis.

But what other gems lay unexamined? The key to success whenever you build anything is understanding the tools that are available to you, what they are capable of, and how to choose the right one. I'm going to run through some that you may not know about and provide examples of how we've used them in SCA.

_.defaults()

The _.defaults() method sets any undefined values in an object with some defaults that you provide without overwriting any properties that have been set (even if the keys match).

It's particularly useful when you want to merge together two objects but give one a precendence over the other. The primary place that we use this in SCA is with configuration files, specifically with the configuration for items key mapping.

The ItemsKeyMapping module is used to set standard ways for accessing information about items. Seeing as items come up in various areas of the site, having a standard way to pull details out is a good idea. If you change the way certain values are set (eg how an item's name is set) then you can modify this file (or, rather, extend the module and override a function) and change it, without having to track down all the places you call the item name.

So for fun, let's say I want to change every item's display name to the same thing. I know that there's a function that sets the name (_name). I also know that in ItemDetails > ItemDetails.Model.js, there is the following code:

, getKeyMapping: function()
  {
    if (!this._keyMapping)
    {
      this._keyMapping = _.defaults(Configuration.itemKeyMapping || {}, ItemsKeyMapping.getKeyMapping(Configuration));
    }
    return this._keyMapping;
  }

Do you see what it's doing? It's saying set as a default either the value of itemKeyMapping from the configuration object or set it blank, and then set any remaining unset values by running it through the function.

I've set up a module called Configurator and in it I've put the following:

define('Configurator'
, [
    'SC.Configuration'
  ]
, function (
    Configuration
  )
{
  'use strict';

  Configuration.itemKeyMapping = {};

  return {
    mountToApp: function ()
    {
      Configuration.itemKeyMapping._name = function() {
        return 'GENERIC APPAREL ITEM'
      };
    }
  };
});

Can you tell what's going to happen? When I run gulp local it's going to compile, and the code in ItemDetails.Model.js is going to find my code and set it as a default. In this case, it'll find my new _name() method and set it as the default; when it comes to processing the methods in ItemsKeyMapping.js, it'll ignore it as my one has been set as the default. Thus, it will end up looking like this:

Of course, you could change this to a legitimate function to match your needs (but I'm all about having some fun).

For an illustration of why this is important, change the _.defaults method in ItemDetails.Model.js to _.extend. Note how your changes appear to have been reverted and the items show their true names again. Indeed if you take a look at SC.CONFIGURATION.itemKeyMapping in the frontend (using your developer console), you'll see that the function is being set in ItemsKeyMapping.js and not the Configurator module.

Make sure to change the method back before proceeding. And maybe undo the stuff we did in the configurator, if you've been following along.

I hope you see how powerful _.defaults() is. There may not be a lot of times when you'll need to use it, but it's a pseudo-alternative to _.extend() as while it can merge together multiple objects, it makes the source object somewhat immutable, which, as we've seen, can have its uses.

_.debounce() and _.throttle()

At their simplest, these two methods provide a way to rate limit the calling of a function. In particular, debouncing is useful for cutting down redundant AJAX calls, and throttling is useful for preventing performance degradation from repeated JavaScript processing.

_.debounce()

By using _.debounce(function, wait, [immediate]), you create a new function of the one you pass to it, delaying its execution for the specified amount of time.

Debouncing becomes useful when you expect something to trigger the function repeatedly and frequently, but only want it to trigger once calls to it have stopped. This is particularly crucial to control when the function makes API requests as it repeated calls could cause performance issues.

A good example of us using this functionality is the type ahead search functionality. As you'll know, once a customer types in more than three characters into the search box, the site performs a keyword search on that string and returns suggestions. We have configured this functionality to wait a set amount of time before sending the requests so that every keystroke doesn't immediately trigger a search. Imagine, for example, a shopper typing in 10 characters: that would be 7 calls to the API. Whereas, by waiting a small period of time, we can cut that down to maybe one or two.

To see and tinker with this code yourself, a took a look into ItemSearcher > ItemsSearch.View.js, specifically:

, getTypeAheadConfiguration: function ()
  {
    var self = this;

    //@class ItemsSearcher.View.TypeAheadConfiguration
    return {
      //@property {Function} source
      source: _.debounce(_.bind(self.loadSuggestionItemsSource, self), 500)
      //@property {Function} displayKey
    , displayKey: _.bind(self.getSelectedItemDisplayText, self)
      //@property {Object} templates
    , templates: {
        suggestion: _.bind(self.getSuggestionItemTemplate, self)
      }
    };
    // @class ItemsSearcher.View
  }

Do you see the 500 value in the _.debounce? So that's 500 milliseconds it waits before it runs the function — note that it only runs the most recent call of the function (ie, when the user types in more characters, it only searches for the most recent string). Change the value to something else (eg 50 or 5000) and see what happens.

Setting it very low (eg 50) causes every keystroke to trigger a search immediately. If you keep the network tab open in your developer console, then you'll see it sending XHRs over and over again. While the payload back from the server is usually small, this is still bad for performance because of the frequency of the requests.

Setting it very high (eg 5000) means that it will be a considerable amount of time (5 seconds) before the search requests are made. In the example of type ahead, this detracts from the functionality's power — so a happy medium needs to be found (hence we set this, by default, to 500 milliseconds).

_.throttle()

So what about _.throttle()? Throttling a function does a similar job to debouncing, but instead of waiting for calls to stop — it will, instead, run but only once during the interval time you set.

This is the crucial difference between the two: debouncing waits the interval for the function to stop being called before making a request, whereas throttling sends one (and only one) request during that interval time. Thus, debouncing a request could mean that you potentially wait a long time before a request is made; throttling it means that requests are made regularly and at a steady rate.

We use throttling in a few places. One place is in Home > Home.View.js; when the user resizes the window, we trigger functions to show content (again). If we didn't throttle this then constantly resizing the window would call this function hundreds of times a second, which is unnecessary and would be bad for performance.

Another example — a particularly good one — is with the loading animation that we attach to the cursor. Now, unless you've disabled this functionality (eg by following by guide on customizing the loading icon and went for the 'blocking' option) you will find an instance of throttling in jQueryExtras > jQuery.ajaxSetup.js. I'm not going to include the entire function, just the relevant part:

$body.on({
  // On mouse move, we update the icon's position, even if its not shown
  mousemove: _.throttle(function (e)
  {
    mouse_position = {
      top: Math.min($body.innerHeight() - icon_height, e.pageY + icon_width)
    ,  left: Math.min($body.innerWidth() - icon_width, e.pageX + icon_height)
    };

    $loading_icon.filter(':visible').css(mouse_position);
  }, 50)
...
});

So, what we're doing is adding an event listener for whenever the cursor moves, which is a scary thing in JavaScript because of how often that will happen. What we want to do is have the loading animation track the cursor, so that when something is loading the animation shows right next to the cursor. However, what we don't want to happen is for the user's device to process this JavaScript hundreds of times a second; so we throttle it.

If you want to get an idea of what this is like without throttling, set the value (50) much lower. You could also put a console.log() in the code too, to get a sense of how often this event is triggering. It's a lot, and 50 milliseconds is a good compromise — it cuts down on a lot of potential requests, while still doing a good job of tracking the cursor.

_.wrap()

Not the tasty flatbread, but instead a tasty function that is a crucial part of extending code in SCA.

I say 'extending', but I mean that in a looser sense than literally using _.extend(). We use _.wrap when we want to add to an existing function without editing the code of that function directly. I've mentioned it before, as it is used in one of the three ways you can extend JavaScript.

For example, if I wanted to add new properties to an existing view's getContext function, then I would do something like this:

SomeView.prototype.getContext = _.wrap(SomeView.prototype.getContext, function(fn)
{
  var context = fn.apply(this, _.toArray(arguments).slice(1));

  context.myFavouriteNumber = 7;
  context.myFavouriteFruit = 'Apple';

  return context;
});

While prototyping allows us to add new properties to existing objects, this method does not work by itself if the thing you're modifying is a function. What we're doing here is taking the getContext function and then recreating it. First we pass it the original function, then we strip (slice) out the parts we don't need, and then add in our additional code.

_.map()

If you've ever done any work on a SuiteScript model, then you would have likely come across us mapping a result from the server and then returning it. For example, the model in the MyReviews tutorial looks something like this:

list: function ()
{
  // define search filter
  ...

  // define search columns
  ...

  // define record type to be searched
  ...

  return _.map(search, function(result) {
    return {
      reviewid: result.getValue('internalid')
    , rating: result.getValue('custrecord_ns_prr_rating')
    , text: result.getValue('custrecord_ns_prr_text')
    , itemid: result.getValue('custrecord_ns_prr_item_id')
    , created: result.getValue('created')
    }
  })
}

If you log before after you map the results you get the following (I'm only going to show two results to save space):

// Before
[
  {
    "id": "4",
    "recordtype": "customrecord_ns_pr_review",
    "columns": {
      "internalid": {
        "name": "4",
        "internalid": "4"
      },
      "custrecord_ns_prr_rating": 1,
      "custrecord_ns_prr_text": "qweqwee12e12e",
      "custrecord_ns_prr_item_id": 8044,
      "created": "9\/16\/2016 4:10 am"
    }
  },
  {
    "id": "1",
    "recordtype": "customrecord_ns_pr_review",
    "columns": {
      "internalid": {
        "name": "1",
        "internalid": "1"
      },
      "custrecord_ns_prr_rating": 5,
      "custrecord_ns_prr_text": "Great",
      "custrecord_ns_prr_item_id": 8033,
      "created": "9\/16\/2016 4:05 am"
    }
  }
]

// After
[
  {
    "reviewid": "4",
    "rating": "1",
    "text": "qweqwee12e12e",
    "itemid": "8044",
    "created": "9\/16\/2016 4:10 am"
  },
  {
    "reviewid": "1",
    "rating": "5",
    "text": "Great",
    "itemid": "8033",
    "created": "9\/16\/2016 4:05 am"
  }
]

It's pretty obvious the result of mapping is doing, but what's happening?

Using _.map is like a shorthanded version of a for loop that builds a new object out of the one you pass it using your specified iteratee. In the above example, we want to take the results from the search and build an object containing only the values we want and (perhaps more importantly) the keys that we want. It also provides us a way to put the data into a structure that we want.

Keep in mind that this function can do virtually anything to the original object. For example, if you wanted to manipulate each of the values (eg to append something, transform it, make an API call, etc) then you could. For example, if you wanted to strip out the time the review was created, leaving only the date then you could use this opportunity to transform the created values thusly:

, created: result.getValue('created').split(" ", 1)

This saves you from having to do it later, and you know that the data being supplied to the models is what you want.

Final Thoughts

Underscore includes a lot of functions and we use many of them throughout SCA. They are also form the backbone of, ahem, Backbone — indeed, many of the Underscore methods can be applied onto views, models and collections directly. (Read the Backbone docs to find out which ones.)

I've picked a few which you may have used in SCA but not quite understood why (ie because you copied it from somewhere else) or you've seen them used somewhere and wondered what they did. Debouncing and throttling was particularly interesting to me when I first found out what they did, and seeing what happens when you don't have them in your code was particularly illuminating.

If you're interested in learning more, you should, of course, take a look at the official documentation, but I would also recommend taking a look at their annotated source. The annotated source not only shows the code of each of the functions (ie how they're built) but includes commentary on what specifically they're doing and how they do it.