Last Updated: February 25, 2016
·
5.902K
· sorella

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)

4 Responses
Add your response

Good explanation! Thanks for sharing

over 1 year ago ·

The code formatting for this post is a bit off: http://cl.ly/image/0I1Y0v363m33

over 1 year ago ·

Thanks for sharing.

over 1 year ago ·

ricardo, sometimes it happens to me on chrome.

over 1 year ago ·