Promises, Promises Pt. 3
Composing in ES6

I don’t know what your familiarity level is with functional programming, and I’m not planning to go too far into it. I’m currently taking a specialization on the subject on Coursera, but if you’re totally new to the concept, I would really recommend the YouTube channel Fun Fun Function by Mattias Petter Johansson (a.k.a. mpj). He breaks down the basics of functional programming reeeeeeaally well. For now we’re going to do what functional programming does best: compose.

Promises are a little like JavaScript Arrays in some ways. I mean, they’re both structures that wrap around values, and they both have a lot of what we call “higher order functions” that take in smaller functions and produce new structures. There’s a couple of functions that it helps to be familiar with: forEach and map. forEach iterates through the array and does something with each value.

var array = [0, 1, 2]

array.forEach(function(val){
	console.log(val * 2);
});

// CONSOLE: 0
// CONSOLE: 2
// CONSOLE: 4

This is very imperative, very performative. Take the value, do something with it. map is little more functional, more informative. it takes in a transformer function that transforms a value into a different kind of value, and returns a new array with all the values passed through that transformer function.

// Make an array
var originalArray = [0, 1, 2];

// Use the map function to create a new transformed array
var transformedArray = originalArray.map(function(val){
	return val * 2;
});

// Original array hasn't changed
console.log(originalArray); 
// CONSOLE: [0, 1, 2]

// New array has the transformation
console.log(transformedArray); 
// CONSOLE: [0, 2, 4]

// Yep, our map function gave us a totally new array.
console.log(originalArray === transformedArray); 
// CONSOLE: false

In this case, I wasn’t super transparent about the then and catch functions. In the last post we treated them like the forEach method of JavaScript Arrays, i.e., just use it to access the value and do something with it. But then can also act like the Array’s map function, transforming the promise. Take a look below:

// Make a promise
var originalPromise = Promise.resolve(3);

// Use the then function to create a new transformed promise
var transformedPromise = originalPromise.then(function(val){
	return val * 2;
});

originalPromise.then(function(val){
	console.log(val);
	// CONSOLE: 3
});

transformedPromise.then(function(val){
	console.log(val); 
	// CONSOLE: 6
});

console.log(originalPromise === transformedPromise); 
// CONSOLE: false

So we can use then like a map function! What about catch? Can we transform the error? Let’s take our luckyPromise from before and switch it, so that when it has a value, that becomes the error, and when it gets an error, that’s valid.

var newPromise = luckyPromise.then(function(val){
	throw new Error("Wait What? " + val);
}, function(error){
	return 0;
});

newPromise.then(function(val){
	console.log(val); // Now only fires where the first promise had an error
}, function(error){
	console.error(error); // Will show "Wait What? " and the number.
});

But wait, there’s more! There’s a third function that then acts similar to: flatMap. Now, I do wish to the gods that JavaScript arrays had the flatMap function. Seriously, I don’t know why it’s not there. But the idea is that flatMap would take in a function that takes in one of the array values and return a new array. Then flatMap would return a new array that concatenated all those values together. Instead of returning an array of arrays, it just returns an array of values.

var originalArray = [0, 1, 2];

function mapperFunction(val){
	return [val, val];
}

var mapped = originalArray.map(mapperFunction);
console.log(mapped);
// CONSOLE: [[0, 0], [1, 1], [2, 2]]

var flatMapped = originalArray.flatMap(mapperFunction);
console.log(flatMapped);
// CONSOLE: [0, 0, 1, 1, 2, 2]

It may seem a little weird to talk about a function that doesn’t exist, but it does in lots of other libraries like this. In fact, it’s a pretty integral part of the definition of a monad which is this big thing in the functional world. Most other languages with functional collection wrangling have it, like LINQ (though they call it SelectMany). Anyway, my point is this: if Promises had a function like that, it would take in a function that returns a promise, but instead of returning a promise of a promise of a value, it just returns a promise of that value. Well, then and catch do this implicitly! If the return value of the function is a promise, it just shortens that down.

var firstPromise = Promise.resolve(3);
var secondPromise = Promise.resolve(2);

var composedPromise = firstPromise.then(function(firstVal){
	var internalPromise = secondPromise.then(function(secondVal){
		return firstVal * secondVal;
	});

	return internalPromise;
});

composedPromise.then(function(composedVal){
	console.log(composedVal); // 6
});

That’s right! Your first instinct is that composedVal should be the internalPromise, since that’s what’s returned from that function, but promises see that you’re returning a promise and compose a flatMap instead of a regular map. One of the great parts of that is that you can use this to compose promises, or even provide a fallback promise.

function getFromLocalCache(id){
	//  returns a promise of the info from cache, or fail if it's not there
}

function getFromApi(id){
	// returns a promise of an API call with a much higher chance of success.
}

function getFromAnywhere(id){
	// Try to get this item from local cache.
	var localCachePromise = getFromLocalCache(id);
	var combinedPromise = localCachePromise.catch(function(){
		// Okay, we didn't have it local.  Let's get it from the API.
		var apiPromise = getFromApi(id);
		return apiPromise;
	});
	return combinedPromise;
}

Combining Promises

We can see how to use then to perform some of this combining. There are two more functions from the Promise API that I want to point out: Promise.all and Promise.race.

All

Promise.all is a great function that turns an Array of promises into a promise of an Array. It resolves when all of those promises have resolved, or rejects as soon as any of them rejects. Let’s take my nacho example and say that I have sent out two roommates, one for chips and one for cheese.

function getChips(){
	// returns promise of chips
}

function getCheese(){
	// returns promise of cheese
}

var chipsPromise = getChips(),
	cheesePromise = getCheese();

var chipsAndCheesePromise = Promise.all([chipsPromise, cheesePromise]);

var nachoPromise = chipsAndCheesePromise.then(function(items){
	var nachos = makeNachos(trustyPan, items[0], items[1]);
	return nachos;
}, function(error){
	console.log("There will be no nachos today.");
})

Race

I will be honest that I have not used this one in production yet, but Promise.race is the “logical or” to Promise.all’s “logical and”. If all the items reject, this will reject with an array of their reasons, but if any of the values resolves, this will resolve.

var nachosPromise = delayer(function(){ return "nachos"; }, 
	Math.random() * 2000);
var iceCreamPromise = delayer(function(){ return "ice cream"}, 
	Math.random() * 2000);

var snackPromise = Promise.race([nachosPromise, iceCreamPromise]);

snackPromise.then(function(snack){
	console.log("I'm eating: " + snack);
});

I usually found that, if I requested the promise, I probably need it, but this might be a good way of prioritizing content based on when it came back while waiting for the other stuff to show.

Summary

So, this concludes the ES6 Edition of Promises. In the following post, I’ll do the same thing, but for Angular.js syntax, then we’ll talk about some real world problems I had and how I composed promises to solve them.

"index-pl.html"