There is a common task in many software development cases – get one list and transform it to another list. For example, implementing consesus protocol in a blockchain requires a function that receives an array of transactions and produce another list of transactions which it considers correct. In this article we will examine a case where we are given a data set with contacts of people based on which we need to produce another list of related one time tokens to send to a client. As data transformation tasks tend to become intricated, implementation will be done in accordance to functional programming paradigm to make code simplier to understand and reason about.
Recently I had to carry out a refactoring of a JavaScript code which have been typical for many years:
First of all it looks monolytic, hard to comprehend, therefore wtf meter is overshooting. There are no logical steps and, in order to find out how it works, you need to execute the whole function in your mind. Loops can be pretty big so we have to put comments beside curly braces to identify where a loop ends.
Basically what it does is loops through some data, collects unique emails and generates an array of tokens for them.
We use the yield
keyword to get a value from asynchronous functions before continue to execute the rest of a function.
So lets fix these downsides converting our code to functional style. One of the traits of functional programming is to make a value immutable which is passed to a function which transforms it in some way and in turn pass to another function and so on until the required structure is shaped. It is the opposite to conventional approach where you assign a new value every loop.
Lets start from the part that I like the most – getting our code free from for
statements.
We get rid of the whole cycle and instead engage JavaScript function map (node.js supports it since v4) which will interate through an array and produce a new array where each element will be a result of the callback. This function is synchronous and will block the whole event loop. Keep this in mind and consider dividing into chunks large data sets.
Output:
[ [ undefined, 'michelle@gmail.com', '' ],
[ undefined, 'johann@gmail.com', 'johann-md@gmail.com' ] ]
So as a result we got an array of arrays of emailAdresses. Our target structure is a plain list, so lets convert this 2 dimensions array into a flat list. We are going to engage two new JavaScript functions: reduce and concat. Reduce iterates through an array and produce a single value and concat simply merges two arrays. Combination of them is a single plain array.
Output:
[ undefined,
'michelle@gmail.com',
'',
undefined,
'johann@gmail.com',
'johann-md@gmail.com' ]
Great! Flat list is much easier to understand and manipulate. It is tempting to start producing target structure, but before that let’s perform a clean up – remove duplicates and empty values. In order for that we will use the filter function – it will remove all elements which do not satisfy with a condition.
Output:
[ 'michelle@gmail.com',
'johann@gmail.com',
'johann-md@gmail.com' ]
Pretty neat! Now we are ready to build the final structure. So we aim to an array of dictionaries with two properties - emailAddress and token. Token is a one time JWT token which we are going to store in MongoDb. So this will require asynchronous requests as MongoDb API is asynchronous. Let’s engage Bluebird’s function map which can iterate through an array and execute a Promise for each element assigning back the return value.
Output:
[ { electronicAddress: 'michelle@gmail.com',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YWx1ZSI6Im9iYW1hQGdtYWlsLmNvbSIsImlhdCI6MTQ4OTczMzg1Mn0.lfoJ6zxUmVNE2AZ2nSY6fgoGEu6wN_xqemu3k1Z4XyQ' },
{ electronicAddress: 'johann@gmail.com',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YWx1ZSI6InRydW1wQGdtYWlsLmNvbSIsImlhdCI6MTQ4OTczMzg1Mn0.Rw1dZsx4NIosZVpWFREQSg7Wz8C5i2_b15CrNrZjHNc' },
{ electronicAddress: 'johann-md@gmail.com',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YWx1ZSI6InRydW1wLXByZXNpZGVudEBnbWFpbC5jb20iLCJpYXQiOjE0ODk3MzM4NTJ9.xZJAX35U2_5kL-LLSn2Jju6XGj9nqQ475TU4c7RgJd0' } ]
Well we have met our goal and refactored the imperative code to functional fashion. All the cycles, temporary variables and buffers are boiled down to a chain of functions which separates concerns to different logical blocks. Next time you look into this code you might omit the rest of the code except the main function generateResponse
. This code will have less bugs as it avoids to keep state in variables and instead always produce a new value.