[フレーム]
Last Updated: February 25, 2016
·
6.197K
· 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 ·

AltStyle によって変換されたページ (->オリジナル) /