Skip to content

Instantly share code, notes, and snippets.

@spion
Forked from ithinkihaveacat/README.md
Last active January 2, 2016 05:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spion/8254661 to your computer and use it in GitHub Desktop.
Save spion/8254661 to your computer and use it in GitHub Desktop.

Comparison of two different approaches to structuring code that uses Promises.

Version using .then(), from Complex task dependencies:

files.getLastTwoVersions(filename)
    .then(function(items) {
        return [versions.get(items.last), 
                versions.get(items.previous)];
    })
    .spread(function(v1, v2) { 
        return diffService.compare(v1.blob, v2.blob)
    })
    .then(function(diff) {
        // voila, diff is ready. Do something with it.
    });

With wrapped functions

items = files.getLastTwoVersions(Promise.resolve(filename));
v1 = versions.get(items.get("last"));
v2 = versions.get(items.get("previous"));
diff = diffService.compare(v1.get("blob"), v2.get("blob"));
diff.then(function (s) {
    // voila, diff is ready. Do something with it
});

3rd Alternative (full source)

function property(name) { 
  return function(obj) { return obj[name]; }
}

items = files.getLastTwoVersions(filename);
v1 = files.then(property('first')).then(versions.get);
v2 = files.then(property('last')).then(versions.get);
diff = Promise.join(v1.then(property('blob')), v2.then(property('blob')))
              .spread(diffService.compare); 
diff.then(function(s) { /* voila */ });

In general, you want the inputs of your function to always be values and let then and spread do the promise unpacking. Its almost equally easy to write diffService.compare(v1.get("blob"), v2.get("blob")); and Promise.join(v1.then(property('blob')), v2.then(property('blob'))).spread(diffService.compare);, except the second one is more general.

Instead of writing separate functions that work with promises, write higher order functions that can be passed to then and spread. Note that these (such as the property function) do not unpack promises. Separation of concerns :)

With ES6 arrow functions defining higher order functions becomes quite elegant:

let property = name => obj => obj[name];

items = files.getLastTwoVersions(filename);
v1 = files.then(property('first')).then(versions.get);
v2 = files.then(property('last')).then(versions.get);
diff = Promise.join(v1.then(property('blob')), v2.then(property('blob')))
              .spread(diffService.compare);
diff.then(s => /* voila */);

And perhaps quite unnecessary :)

items = files.getLastTwoVersions(filename);
v1 = files.then(items => versions.get(items.first));
v2 = files.then(items => versions.get(items.last));
diff = Promise.join(v1, v2).spread((v1, v2) => 
    diffService.compare(v1.blob, v2.blob));
diff.then(s => /* voila */);
@ithinkihaveacat
Copy link

@spion I do prefer the 3rd version to the 1st, but I think the wrapped version has very much better readability in almost all cases.

For example something like

p = foo(baz(x), bar(y, bar(a, b)));

reads a lot better that a .then() equivalent, which would require you to manually track the intermediate values, and nest the .then() calls. And even better, Promise., .then(), .spread(), etc. does not appear at all in the source code. (I don't think it a good idea to be able to use Promises without ever knowing that you're doing so, but it's helpful to declutter your code when you're puzzling out an algorithm.)

Also, with wrapped functions you can also do normal-looking method calls. This does expose some of the implementation, but it is at least possible.

An example where string methods are called on a Promise that resolves to a String:

p = Promise.resolve("hello");
T(p, String); // monkey patch String methods onto p...

q = p.toUpperCase();
q.then(function (s) { console.log(s); }); // -> HELLO

Array methods like .reduce() also work as you'd expect:

add = P(function (i, j) {
    return i + j;
});

p = Promise.resolve([1, 2, 3]);
T(p, Array); // monkey patch Array methods onto p...

q = add(Promise.resolve(10), p.reduce(function (a, i) { return a + i; }));
q.then(function (i) { console.log(i); }); // -> 16

T() is defined:

function T(promise, klass) {
    Object.getOwnPropertyNames(klass.prototype).forEach(function (m) {
        promise[m] = P(klass.prototype[m], promise);
    });
}

@spion
Copy link
Author

spion commented Jan 7, 2014

But you will have to wrap all language built-ins. That gets tedious real fast, not to mention that the performance suffers too, and at that point I'd rather use ClojureScript :)

Arrow functions likely make the entire thing super-short.

files.getLastTwoVersions(filename)
  .then(items => [versions.get(items.first), versions.get(items.last)])
  .spread(v1, v2 => diffService.compare(v1.blob, v2.blob));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment