Mapping “future” values in JavaScript, a more functional approach.

Mapping “future” values in JavaScript, a more functional approach.

Many JavaScript developers do not deal directly with Promises any more, things like RxJS and/or the async/await language support have saved them from that. But many developers around the world are not that lucky yet, and for different reasons still use Promises directly. Regardless of these reasons, if you fall into this category, I want to help you today to write your code a bit better. We will use functional programming techniques, but in order to not create frustration and confusion, or be too theoretical, I will stay away from terms like MonadsFunctorsMorphisms, etc.

In this article, I am going to use functional programming techniques that can guide you to a more robust and easy to maintain codebase. I am not going to talk about Iterator/Generators like I did here (https://itnext.io/iterators-and-generators-do-have-a-place-in-modern-javascript-d4cb589b491). I am going to use straight Promises, and a bit of functional “magic”.

One thing that I want to be honest about from the get-go is, Promises are impure in nature, so I will not use terms like pure functional programming, or suggest that what is shown here is functional programming, I will think of it as “functional techniques”. Let’s dive right in.

In order to keep things simple, let’s work with the following in-memory data:

const people = [{
    id: 1,
    firstName: 'John',
    age: 34
  }, {
    id: 2,
    firstName: 'Rose',
    age: 25
}];

const cars = [{
    ownerId: 1,
    make: 'Ford',
    year: 1988
  }, {
    ownerId: 2,
    make: 'Audi',
    year: 2001
}];

The problem that we will try to solve is: Given a person’s id, the Make of their only car needs to be printed in the Console.

The traditional Promise way:

Let’s declare two functions that will use the above variables, but that might as well retrieve the information from a database, or network call.

const getPersonById = id => Promise.resolve(people.filter(p => p.id   === id)[0]);

const getCarByOwnerId = id => Promise.resolve(cars.filter(c => c.ownerId === id)[0]);

Both methods return a Promise, since that is what the fetch function (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) would return, or jQuery.ajax() or Angular 1.x’s $http.get(), etc.

The traditional code would look something like this:

getPersonById(personId)
  .then(person => getCarByOwnerId(person.id))
  .then(car => car.make)
  .then(console.log)
  .catch(e => console.log(`The following error occurred: ${e}`));

A more functional way:

We are going to add a Map function to Promise.prototype that will allow us to apply transformations to a Promise even before it is resolved, something like “Transform the future value in this way…”.

Promise.prototype.map = function (fn) {
  return new Promise((resolve, reject) => this.then(x =>                
  resolve(fn(x))).catch(e => reject(e)));
};

As you can see, this function will take a “mapping” function and will return a new Promise that will resolve the value resulted from applying the transforming function over the value the original Promise is going to resolve. It also makes sure to reject the Promise in case of an error when resolving this original Promise. How could we use it?

Promise.resolve(3).map(x => x + 2).then(console.log);

We created a Promise that resolves to the value 3, and without attaching a then, we said we wanted to add 2 to that future value (3) when it resolves. The result, a new Promise that when resolved, will give you the value 5 (2 + 3).

Let’s start taking some functional approaches. I was going to add a reference to ramdajs (https://ramdajs.com/) but then I realized that a lot of teams (or team managers) are skeptical (for some reason) to pull new libraries into existing projects. So the functions that I was going to use from ramdajs, we will implement them ourselves. Just realize that they will not be as robust as ramdajs’s, but it will also help us not leave anything to the imagination.

const prop = propName => obj => obj[propName];

The above function called prop will take a property name and will return a new function that when called by passing an object, will return the value of that property for that given object. How would that work? Let’s see:

const makeOf = prop("make");
const make = makeOf(cars[0]);

console.log(make);

// 'Ford'

We are ready to improve the solution to our original problem already. Let’s see what that looks like:

getPersonById(personId)
  .map(person => getCarByOwnerId(person.id))
  .map(prop('make'))
  .then(console.log);

// 'Ford'

So far, we have only moved from thens to maps, bear with me, this transition will help you understand how we arrive at the final version of the code.

One thing to note here is that within getPersonById and getCarByOwnerId we are still referencing the variables people and cars respectivelywhich are outside of their definition, something forbidden in functional programming (known as one kind of side effect). We will fix that later.

There are libraries, like Folktale (https://folktale.origamitower.com/), that already give you types that deal with “future” values and that let you map over them like we did here; in the case of Folktale is called Task. Which by the way is way more powerful and lets you do much more than a Promise right off the bat. Anyway, let’s take this further.

In functional programming, one technique that is heavily used is known as Function Composition. It consists of a series of functions that given an initial value, are executed one after the other one and that each receives as input the output of the previously executed function. A simple compose function could look like this:

const compose = (...fn) => n => fn.reduceRight((acc, fn) => fn(acc), n);

Again, we could have taken it from ramdajs or any other functional JavaScript library, but we are keeping things in-house today! How could we use it?

const toUpper = s => s.toUpperCase();
const trim = s => s.trim();

const toUpperAndTrimmed = compose(trim, toUpper);

console.log(toUpperAndTrimmed(' hello world  '));

// 'HELLO WORLD'

Very frequently when composing functions, we would like to pre-apply certain parameters to a function since we know them ahead of time, and only leave the last parameter to be supplied at runtime. There is a function that allows us to convert any function with multiple arguments into a function that can be partially applied. This function is usually called curry (there is a difference between currying and partial application https://stackoverflow.com/questions/218025/what-is-the-difference-between-currying-and-partial-application). Although I will provide a simple implementation of this curry function, do not concern too much with the details of it, it is enough to know what it does. From the link above: “Currying is converting a single function of n arguments into n functions with a single argument each”. This basically means that you will obtain the final result after you have passed all parameters the original function defines.

const curry = (fn) => {
  const arity = fn.length;
  return function $curry(...args) {
    if (args.length < arity) {
      return $curry.bind(null, ...args);
    }
    return fn.call(null, ...args);
  };
};

Also in order to favor composition, let’s create a method called map.

const map = curry((fn, mappable) => mappable.map(fn));

This map function takes a function (transformation) and an object that is known to have a map method (trusting an object will have a certain method in this way is usually referred to as duck typing). It calls the map method in that mappable object so the given transformation is applied. As you can see, we have used curry, if we had not, then we would have had to write the map function like this:

const map = fn => mappable => mappable.map(fn);

Next let’s define a function that given an array, returns its first element:

const head = a => a[0];

Now we will add a function that given a predicate and an array, it filters and takes all elements that meet the predicate criteria in the array:

const filter = curry((predicate, a) => a.filter(predicate));

Let’s now define a function that will return the first element that matches a predicate.

const first = predicate => compose(head, filter(predicate));

We will redefine the functions getPersonById and getCarByOwnerId as following:

const getPersonById = id => first(p => p.id === id);

const getCarByOwnerId = id => first(p => p.ownerId === id);

We are going to define two new functions that given a source array, and a second piece of information, can obtain a person by id, and a car by owner id respectively.

const getPersonByIdFrom = curry((source, id) =>   
               Promise.resolve(getPersonById(id)(source)));

const getCarByOwnerIdFrom = curry((source, person) =>  
               Promise.resolve(getCarByOwnerId(person.id)(source)));

Take a moment to understand what we have done. Also noticed that we are using Promise.resolve to return a Promise like we were doing originally. One important thing about functional programming is the creation of many small functions (that are focused on doing one thing and are very good at it) and then composing them (it is a great reuse technique). This leads you to create software in a declarative way (instead of the traditional imperative way), in which your final product is definitely more robust and flexible. The problem is that almost all of us were taught the imperative thinking and are biased toward it, and this approach challenges us and sometimes frustrate us. Are you up for the challenge?

Next, we compose what we have so far to create the function in charge of solving our problem.

const getMakeForOwnerId = 
               compose(map(prop('make')), map(getCarByOwnerIdFrom(cars)),         
                       getPersonByIdFrom(people));

Read right to left inside the compose function to follow the flow and what is happening. Now the makeForOwnerId is only waiting for a person id to perform all composed behavior. And finally, we unleash the “magic” and log the result to the Console.

getMakeForOwnerId(personId)
  .then(console.log);
  .catch(e => console.log(`The following error occurred: ${e}`));

// Ford

I understand this style of programming and mindset is not for everyone. I also understand that complexity (and bias to the known) makes many developers shy away from it. But if you are at least curious about it, give it a try, and at the very least, it will be another tool in your toolbelt.

Happy Coding!

Benoît Lahoz

Indépendant78 followers

1y

Great article! Thank you. What about wrapping it in a `Future` class that would return a `Result` (either a `Success` or `Failure`) and then be convertible to `Maybe` or `Either` monads? Struggling these days with map and flatMap implementation on this problem... Your `Promise.map` function is a lifesaver.

Like
Reply

To view or add a comment, sign in

More articles by Eric Rey

Others also viewed

Explore content categories