Skip to content

Instantly share code, notes, and snippets.

@ithinkihaveacat
Last active January 2, 2016 02:49
Show Gist options
  • Select an option

  • Save ithinkihaveacat/8239436 to your computer and use it in GitHub Desktop.

Select an option

Save ithinkihaveacat/8239436 to your computer and use it in GitHub Desktop.

Compared to the .then() style, this approach:

  • Looks almost identical to the non-Promise version--you end up with imperative code, just like the non-Promise equivalent, instead of long .then() chains. (Unfortunately you do still need to use .catch() to handle exceptions/rejections.)
  • Supports functions of multiple arguments, including functions where some of the arguments are Promises, and some not.
  • Works by transforming a normal-looking function--if you have a pure function that is not asynchronous, with primitive types as arguments and a primitive type as a return value, you don't even need to know that Promises are involved.
  • With a helper function, it also supports fairly natural-looking method calls where the arguments and return values are also Promises. (Though admittedly this does get a bit ugly...)

// Works on Chrome 32+.
// Some simple arithmetic functions, without Promises:
function add(i, j) { return i + j; }
function plusone(i) { return i + 1; }
function log(s, i) { console.log(s, "=", i); }
var a = 3, b = 1, c = 8;
log("A.1", add(a, add(b, plusone(c)))); // -> 13
// The same arithmetic, with the functions transformed to support Promises:
addx = P(add); // P() defined below
plusonex = P(plusone);
logx = P(log);
var ax = Promise.resolve(3), bx = Promise.resolve(1), cx = Promise.resolve(8);
logx("B.1", addx(ax, addx(bx, plusonex(cx)))); // -> 13
// Variables work exactly as you'd expect:
n = add(3, 4);
log("A.2", add(2, n)); // -> 9
log("A.3", add(n, 9)); // -> 16
n = addx(Promise.resolve(3), 4);
logx("B.2", addx(2, n)); // -> 9
logx("B.3", addx(n, 9)); // -> 16
// If you have an asynchronous operation, return a Promise:
function foo(i) {
return new Promise(function (resolve) {
resolve(i);
});
}
foox = P(foo);
logx("B.4", addx(8, foox(3))); // -> 11
function P(fn, self) {
return function () {
var args = Array.prototype.slice.call(arguments, 0);
return new Promise(function (resolve, reject) {
if (self) {
args.unshift(self);
}
Promise.all(args).then(function () {
var obj;
try {
if (self) {
obj = fn.apply(arguments[0].shift(), arguments[0]);
} else {
obj = fn.apply(null, arguments[0]);
}
} catch (e) {
reject(e);
}
if (obj instanceof Promise) {
obj.then(resolve, reject);
} else {
resolve(obj);
}
}, reject);
});
};
}
void 0;
// Stuff that's pretty hacky, but more or less impossible with .then().
// Note that a lot of this output appears out of order because, well, asynchrony.
function logx(p) {
p.then(function (s) {
console.log(s);
});
}
// Promises and methods
p = Promise.resolve("hello");
q = T(p, String); // asserts that p will resolve to object of type String
logx(q.toUpperCase()); // -> "HELLO"
logx(addx(3, q.indexOf("o"))); // -> 7
r = T(Promise.resolve(""), String);
s = r.concat(q.slice(2), q.slice(2)); // resolves to "llollo"
logx(s); // -> "llollo"
t = T(s, String); // return value of r.concat() is a regular Promise, need to assert that it is a string
logx(t.indexOf("ollo")); // -> 2
// Property access
p = T(Promise.resolve({ foo: "bar" }), Object);
// p.foo doesn't exist yet, so need the .get() workaround...
logx(p.get("foo")); // -> "bar"
// Exceptions/rejections still need .catch() unfortunately
p = addx(2, plusonex(Promise.reject(3)));
p.catch(function () {
console.log("GOT AN ERROR");
});
logx(p); // doesn't actually do anything, because p got rejected
logx(p); // also doesn't do anything
logx(p); // and so on
function T(promise, klass) {
promise = Promise.resolve(promise); // or modify in-place?
Object.getOwnPropertyNames(klass.prototype).forEach(function (m) {
promise[m] = P(klass.prototype[m], promise);
});
// Workaround for the lack of direct manipulation: foo(user.age) -> foo(user.get("age"))
promise.get = P(function (p) {
return this[p];
}, promise);
return promise;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment