Log and Continue

We often write and modify code that looks like this

const getApiResults = () => {

	const myArr = getApiResults();

	const normalizedApiResults = myArr.filter(filterFn)
		.map(mappingFn)
		.reduce(reducingFn)
		.sort(sortingFn);

	return normalizedApiResults;

};

We can condense this even further by either composing the three transformational steps into a single flatMap invocation and pass through but I find the readability of keeping each step separate helpful as per my Debug Driven Development methodology.

Often when coding we'll want to inspect the array between these steps. It's easy enough to refactor the snippet storing each step's intermediate value in its own variable, adding console logs between or debugger statements at each line.

const filteredData = myArr.filter(filterFn);
const transformedData = filteredData.map(mappingFn);
const reducedData = transformedData.reduce(reducingFn);
const sortedData = reducedData.sort(sortingFn);

console.log(sortedData);

Having to go through this refactoring just for a console log though is frustrating. We can create a better world.

Log And Continue

logAndContinue is the solution to realizing this better world. Here's the code:

Array.prototype.logAndContinue = function() {
	console.log(this);
	return this;
}

A lot simpler than you were expecting right. Here's how we use it.

const myData = myArr.filter(filterFn)
	.logAndContinue()
	.map(mappingFn)
	.logAndContinue()
	.sort(sortingFn);

We could decide to be done here - but where's the fun in that.

Even Better Logging

What if we parameterize a logging string so that when the array is logged, a prefix and postfix string is included around the array value:

Array.prototype.logAndContinue = function(prefixString, postfixString) {
	console.log(prefixString + this + postfixString);
	return this;
}

Now we can decorate the array logging with whatever strings we want:

const myData = myArr.filter(filterFn)
	.logAndContinue('Filtered Array:', 'fin')
	.map(mappingFn)
	.logAndContinue('Mapped Array', 'fin')
	.sort(sortingFn);

We have a bit more control and can log generally better but passing around strings doesn't feel quite right. The most powerful control we can leverage would be through some inversion of control. Instead of passing in a couple of string values to logAndContinue, we can have it accept a function that receives as its argument the array, and returns a string. By default this function will return the array as a string.

Maximum Logging

We can achieve maximal logging nirvana with the following implementation.

Array.prototype.logAndContinue = function(fn = (array) => array.toString()) {
	const array = this;
	const stringToLog = fn(array);
	console.log(stringToLog);
	return this;
}

I know, still very simple. We can continue using this array method minimally.

const myData = myArr.filter(filterFn)
	.logAndContinue()
	.map(mappingFn)
	.logAndContinue()
	.reduce(reducingFn)
	.logAndContinue()
	.sort(sortingFn);

We can also functionalize the string that gets logged:

const arrayLoggerOne = (array) => {
	return 'After filtering the array value is ' + array.toString() + ' fin';
};

// Log only the first element of the array.
const arrayLoggerTwo = (array) => {
	return `The array has ${array.length} elements and after mapping, the
first value of the array is ${array[0].toString()}`;
};

const myData = myArr.filter(filterFn)
	.logAndContinue(arrayLoggerOne)
	.map(mappingFn)
	.logAndContinue(arrayLoggerTwo)
	.sort(sortingFn);

This provides a lot of power to our logging capabilities for arrays through a still easy to use interface.

With an Array.logAndContinue method, we get a far better debugging and array helper method than we otherwise would. Is it always going to be the easiest way to log arrays, no. But when we have a chain of array methods that we just want to inspect, it becomes a lot easier to hop in and hop out with minimal refactoring.