Functional Programming in Javascript: each, keys, isObject

Woohyun Jang
3 min readNov 22, 2020

Last time, We made two functions map and filter. We combined them and implemented the user’s logic simply.

In this chapter, we’ll be going to extract their common logic and make it safer. The common concept of them is that it traverses collection and do something. So we can make a function called ‘each’.

function each(collection, iteratee) {  for (const value of collection) {    iteratee(value);  }}
module.exports = each;

‘each’ function traverse the taken collection and execute the iteratee function for each values.

So we can use the ‘each’ function in ‘map’ and ‘filter’. We should just call the ‘each’ function with the collection and iteratee logic.

// map/index.js
const each = require('../each');
function map(collection, mapper) { const result = []; each(collection, (value) => result.push(mapper(value))); return result;}
module.exports = map;

and

// filter/index.jsconst each = require('../each');function filter(collection, predicate) {  const result = [];  each(collection, (value) => {    if (predicate(value)) {      result.push(value);    }  });  return result;}
module.exports = filter;

When the refactoring is over, let’s check the test cases run well. Nothing wrong. ‘map’ & ‘filter’ work rightly. The reason that we refactored like that is we have to more code in ‘each’ function. ‘map’ & ‘filter’ work well now, with correct values. But if some unexpected value is entered, they’ll make trouble.

Let’s check the ‘each’ function’s logic. It implemented under the premise that the ‘collection’ is an iterable object. So if value such as ‘null’, ‘{}’ come in, current ‘each’ can’t support them.

// functions.test.jstest('map', () => {  expect(map(usersMock, ({ id }) => id)).toStrictEqual([1, 2, 3, 4, 5, 6]);  expect(map(null, ({ id }) => id)).toStrictEqual([]);  expect(map({}, ({ id }) => id)).toStrictEqual([]);});test('filter', () => {  expect(filter(usersMock, ({ age }) => age > 20).length).toBe(4);  expect(filter(null, ({ age }) => age > 20).length).toBe(0);  expect(filter({}, ({ age }) => age > 20).length).toBe(0);});

You’ll see an error message, ‘ TypeError: collection is not iterable’

Iterable and not iterable objects are can access their own values by ‘key’ like that. Of course, if an object is an array or array-like the key is an ‘index’.

object[key]

So If we have an array of their keys, we can traverse it is iterable or not. If input collection is falsy like ‘null’, ‘undefined’, keys will be an empty array so traversing will not be started.

So we need a function that makes the array of object’s keys.

// functions.test.js(...)const keys = require('./keys');(...)test('keys', () => {  expect(keys([0, 1, 2, 3, 4, 5]))
.toStrictEqual(['0', '1', '2', '3', '4', '5']);
expect(keys({ a: 'aaa', b: 'bbb', c: 'ccc' }))
.toStrictEqual(['a', 'b', 'c']);
});

To pass that test case, we can use the ‘Object.keys’ method.

// keys/index.js
function keys(object) { return Object.keys(object);}
module.exports = keys;

Simply writing like this, we can implement ‘keys’ function. Although now the function supports not iterable object, falsy values are not yet.

// functions.test.js
test('keys', () => { (...) expect(keys(null)).toStrictEqual([]); // Fail...});

To solve this problem, we have to check the input object’s type.

function keys(object) {  const isObject = object && typeof object === 'object';  return isObject ? Object.keys(object) : [];}

Or we can also separate the checking logic ‘isObject’.

// isObject/index.jsfunction isObject(object) {  return object && typeof object === 'object';}module.exports = isObject;

and

// keys/index.jsconst isObject = require('../isObject');function keys(object) {  return isObject(object) ? Object.keys(object) : [];}module.exports = keys;

Once the ‘keys’ function has been implemented, let’s apply to ‘each’.

const keys  = require('../keys');function each(collection, iteratee) {  const collectionKeys = keys(collection);  for (const key of collectionKeys) {    iteratee(collection[key]);  }}module.exports = each;

‘each’ function gets the list of collection keys.

  1. If the collection is an iterable object, the collection keys will be an array of indexes.
  2. If the collection is not an iterable object, the collection keys will be an array of object’s keys.
  3. If the collection is falsy value, the collection keys will be an empty array.

Then it will execute iteratee function with traversing collection keys.

Now, there’s no error if we put any value in ‘map’ &‘filter’ function. they are made more safer and reusable!

Github Source Code

--

--