Abandoning return values

Modern promise-based or event-driven JavaScript no longer uses returned values.

Have you noticed the following weird fact: as you get better at JavaScript, your code has fewer and fewer returned values? Remember your initial procedural code:

1
2
3
4
5
function foo() {
return 'foo';
}
var result = foo();
console.log('foo has returned', result);

As you move away from procedural to asynchronous code (see Journey from procedural to reactive JavaScript with stops), notice that promises move away from the returned value to resolved value

1
2
3
foo().then(function (result) {
console.log('foo has produced', result);
});

The next step from promises is event emitters, that break the link between calling a function and generating a value.

1
2
3
4
aFoo.on('foo', function () {
console.log('foo says "hello"'); // 2
});
aFoo.emit('foo'); // 1

There is no longer any value tied specifically to emitting the event 'foo' in line // 1. The console output in line // 2 could have been produced by any call to aFoo.emit('foo');. Compared to promise-returning functions, we no longer can even schedule the next computation step to occur after line // 2 has executed.

Is this a good thing? At some level yes - we are decoupling calling a function from having its result immediately (promises), or from knowing that the specific result has been produced (event emitters). On the other hand, this change is difficult to understand and adjust if you have not used asynchronous and functional programming enough.

On the other hand, you can adjust your expectations. Instead of producing a value, anything you call will execute some code. Using synchronous returned value forced the code to be written after the call. With promise-returning or event-emitting code, the code can be anywhere, giving you the total freedom.

A good transition from procedural code is to write the initial code as a composition of functions

1
2
3
4
5
6
7
8
9
10
11
// procedural
function foo() {
return 'foo';
}
var result = foo();
console.log('foo has returned', result);
// composition
function print(x) {
console.log('foo has returned', x);
}
print(foo());

Once written in this point-free style, it is natural to transform it to async code resolving value instead of returning it.

1
2
3
4
5
6
7
8
var Q = require('q');
function foo() {
return Q('foo');
}
function print(x) {
console.log('foo has returned', x);
}
foo().then(print);

Functional libraries, like Ramda now include methods like pipeP to compose promise-returning methods, similar to sync methods. For example, a pipeline of sync and async methods could be written

1
2
3
4
5
// sync foo, print
var R = require('ramda');
R.pipe(foo, print)();
// promise-returning foo
R.pipeP(foo, print)();

Going in the opposite direction, we might get a wrapped value from methods that traditionally do not return anything. For example when using event emitters, we can get back a promise with a little extra programming, see Promisify event emitter. Or we could use generators to recreate the synchronous-looking code, see database example.

Wrapping guard conditions

Asynchronous results are not the only values we can wrap, removing them from the "normal" source code flow. Often we guard against invalid data inputs using if (variable) { ... } blocks. We can wrap the variable into an object, and replace if JavaScript keyword with a function call, and the statements to execute { ... } with a function to apply. This is how we get a Maybe functor.

1
2
3
4
function Maybe(value, fn) {
return value ? fn(value) : value;
}
// equivalent to if (value) { fn(value); }

Notice that we could use foo and statements directly before

1
2
3
4
var foo = ...;
if (foo) {
console.log(foo);
}

But now we have to use variable foo indirectly

1
2
var foo = ...;
Maybe(foo, console.log.bind(console));

I use this principle to check the input variables a lot, for example see our check-more-types library.

check.then in action
1
2
3
4
5
6
7
8
9
10
11
function isSum10(a, b) {
return a + b === 10;
}
function sum(a, b) {
return a + b;
}
var onlyAddTo10 = check.then(isSum10, sum);
// isSum10 returns true for these arguments
// then sum is executed
onlyAddTo10(3, 7); // 10
onlyAddTo10(1, 2); // undefined

Next step

Notice that Maybe and check.then are functions that take a value and return either undefined or fn(value). What if we wanted to combine multiple functors? It would be nice if a functor Maybe returned another Maybe, just like Promise.then() returns another promise so we can keep chaining them.

Turns out this approach exists and the objects that wrap a value and can be chained are called Monads. An excellent explanation and derivation of wrapping a value in a Functor, then in a Monad can be found in this blog post "From callback to (Future -> Functor -> Monad)".