Concurrency in JS is easy: with Promises.
Promises are a way to represent eventual values. They differ from continuation-passing style (aka Callbacks, Callbacks Everywhere) because they give you real values that you can hold on to, compose, abstract and decouple in your code — so it's easier.
This is a follow-up to my comment in this protip: https://coderwall.com/p/ineqig
1) Promises represent eventual values
var pinky = require('pinky')
var fs = require('fs')
// You just return a placeholder for your value,
// then fulfill or reject your placeholder
// depending on the asynchronous operation later on
function read(filename) {
var promise = pinky()
fs.readFile(filename, function(error, buffer) {
if (error) promise.reject(error)
else promise.fulfill(buffer)
})
return promise
}
2) Promises compose
— Because they're real values, like your String or Array:
function decode(encoding, buffer) {
// We put things into a Promise, so we can
// accept both real buffers *and* eventual ones :D
return pinky(buffer).then(function(buffer){
return buffer.toString(encoding)
})
}
var data = decode('utf-8', read('foo.txt'))
3) Promises can be abstracted over
— because they're values!
// This means we can make any function
// accept a promise without changing any
// of its code :D
function lift2(a, b, f) {
return pinky(a).then(function(valueA) {
return pinky(b).then(function(valueB) {
return pinky(f(valueA, valueB))
})
})
})
function concat(a, b) { return a + b }
var fooBar = lift2(data, fs.readFileSync('bar.txt', 'utf-8'), concat)
4) It's easy to create new combinators
— All of the above properties make it easy.
Bonus: it decouples all your tangled code!
function pipeline(fns) {
return fns.reduce(function(promise, f){
return promise.then(f)
}, promise(undefined) }
}
// This looks better with currying, but you
// can use `.bind(null, x, y)`
pipeline( read('foo.txt')
, decode('utf-8')
, splitLines
, map(toUpperCase)
, joinLines
, write('screaming.txt'))
// Or in parallel
parallel( read('foo.txt')
, read('bar.txt')
, read('baz.txt'))
.then(function(foo, bar, baz) {
return foo + ';' + bar + ';' + baz
})
// Or in any other order you want, just
// build relationships between
// the values using `.then()` and the
// promise library will figure it out :D
5) Promises are standard
So choose any library you want and you'll be able to work with any asynchronous code. Plus if you write a combinator for promises, it works for every promise library, and everything that uses promises, see: https://github.com/killdream/pinky-combinators
6) Working w/ Node-style callbacks is a no-brainer
If you're on Node, you can create a combinator that transforms Node-style into Promise-style in 5 lines of code, but I already wrote one for you: https://github.com/killdream/pinky-for-fun/blob/master/src/index.ls#L42-L51
References and additional reading
Be sure to check the spec (https://github.com/promises-aplus/promises-spec) and the list of conformant implementations (https://github.com/promises-aplus/promises-spec/blob/master/implementations.md)
Great write-ups on the subject includes James Coglan's post (http://blog.jcoglan.com/2013/03/30/callbacks-are-imperative-promises-are-functional-nodes-biggest-missed-opportunity/), and Irkali HisSurnameIsTooDifficultToSpell's post (http://jeditoolkit.com/2012/04/26/code-logic-not-mechanics.html#post)
Written by Quildreen Motta
Related protips
4 Responses
Good explanation! Thanks for sharing
The code formatting for this post is a bit off: http://cl.ly/image/0I1Y0v363m33
Thanks for sharing.
ricardo, sometimes it happens to me on chrome.