That is why _.find() and _.findWhere() are different

During my work on web project for US client, I faced with interesting issue, which at the first look seemed to be related to D3.js/C3.js donut charts, but after deep debugging, I discovered it’s because of nature of Underscore.JS 1.6.0 library/code. I would say, it’s bright example of the fact how important to know main purpose of dedicated API/tool, and implement custom codebase for the future to be working also correct.

Imagine we have data array:

var statuses = [
    "Pending Status A",
    "Pending Status B",
    "Draft Pending",
    "Draft",
    "Closed",
    "Approval Needed"
]

And then we have code:

_.findWhere(statuses, "Draft")

which gives "Draft Pending" and I expected “Draft”.

Then another UnderscoreJS  1.6.0 approach:

_.where(statuses, "Draft")

gives ["Draft Pending", "Draft"] – not really what I needed.

So here is my “Better code”:

var valueToRemove = _.find(statuses, function(arrayValue){
    return arrayValue === "Draft"; // 100% algorithm to find exact "Draft" string value.
});

And here is UnderscoreJS code of _.find(), _.where(), _.findWhere() usage:

// Convenience version of a common use case of `filter`: selecting only objects
// containing specific `key:value` pairs.
_.where = function(obj, attrs) {
    return _.filter(obj, _.matches(attrs));
};

// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
_.findWhere = function(obj, attrs) {
    return _.find(obj, _.matches(attrs));
};

I have to use _.find() because this way, I can avoid using _.matches(). Here is code from UnderscoreJS 1.6.0:

_.find = _.detect = function(obj, predicate, context) {
    var result;
    any(obj, function(value, index, list) {
        if (predicate.call(context, value, index, list)) {
            result = value;
            return true;
        }
    });
    return result;
};
  • This “kinda bug” reproduced on real project data, when first element in array was “Draft Pending”, and second “Draft.
  • When it’s only “Draft”, and all values unique, then all is good, because finding “The Very First and Only One Item” was more than enough. But when “Draft Pending” appeared, we received bug 🙂

Under the hood, Underscore.js 1.6.0 goes to _.matches() function, which in fact works then NOT CORRECT:

// Example
var attrs = "Draft";
var obj = "Draft Pending";

_.matches = function(attrs) {
    return function(obj) {
            if (obj === attrs) return true; //avoid comparing an object to itself.
            
            for (var key in attrs) {
                // key: 0, 1, 2, 3, 4
                // key: "label"
                if (attrs[key] !== obj[key]) // "D" !== "D", "r" !== "r", "a" !== "a", "f" !== "f", "t" !== "t"
                    return false; // for other values, return false } return true; // when Draft finished, it return true } };
            }
    }
}

If you debug the code, u would see, that if u provide Array for the _.findWhere() function and lookup by string value only, u will see,  UnderscoreJS NOT correctly is trying to parse/find string in string[key]. I mean, that _.findWhere() the same as _.where() works ideally with lists aka real JavaScript objects. And if u have Array, you better use _.find().

 

PS/BTW

Here is how UnderscoreJS 1.8.3 code looks of mentioned above functions:

// Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, predicate, context) {
    var key;
    if (isArrayLike(obj)) {
      key = _.findIndex(obj, predicate, context);
    } else {
      key = _.findKey(obj, predicate, context);
    }
    if (key !== void 0 && key !== -1) return obj[key];
  };

// Convenience version of a common use case of `find`: getting the first object
// containing specific `key:value` pairs.
  _.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
  };

// Returns a predicate for checking whether an object has a given set of
// `key:value` pairs.
  _.matcher = _.matches = function(attrs) {
    attrs = _.extendOwn({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
  };

 

Resources

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s