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.
Have a fresh tip? Share with Coderwall community!
JavaScript on a page runs with the privileges of that origin. One altered library, tag manager, or snippet can read inputs, change what people see, and call your APIs as the user.
Security insights from Cside