Write logic, not mechanics
jeditoolkit.com
jeditoolkit.com
Promises are as much a pattern or mechanic as callbacks, but the latter feels far more natural in JS. The `promised` decorator is interesting, but it has problems:
- The application I work on, and have in mind with this, would need just about every function decorated.
- It doesn't account for methods, unless you're default decorating the method, ie.: `Foo.prototype.myMethod = promised(function(/* ... /) { / ... */ });`
- Every decorated function takes a noticable performance hit, because things like `Function#apply` and concatenating `arguments` are relatively slow.
- It doesn't sit well with me that the example rewrites a method on a prototype declared elsewhere: `console.log = promised(console.log)`
Further, the article and library don't even scratch the surface of complicated async flows. Think async versions of common functional-style methods like `map`, `reduce`, etc.
For example, a basic scenario from our own build process is: Scan a directory for template files, read them, compile them, then concatenate the result and write it out.
We used to have a promise library to do all of this, from handling a build process to performing database queries. I discovered Async.js at some point and haven't look back since: https://github.com/caolan/async
I don't too much like the promise pattern this article gives, Q is the definitive implementation: http://documentup.com/kriskowal/q/
[1]http://wiki.ecmascript.org/doku.php?id=strawman:concurrency
For example Q.all is promised(Array) I find later more intuitive.
That's not to say don't use Q! Q is brilliant piece of software and I'd be more than happy to see more people using it.
- I favor maintainability over performance, also keep in
mind that promised function take promises as arguments
and can't do much until they're fulfilled (associated IO
is done) so that small performance hit is insignificant
in most of the cases.
- You can write your own decorators to wrap constructors
and their methods if you need to. That being said, I'd
recommend against, mixing mutable state with logic does
no good in long run. You'll be better of with functional.
- As for map / reduce, promises represent eventual values,
not sequences of them. For that there are streams and I
have explored that area as well:
https://github.com/Gozala/streamer/wiki/stream
I have not wrote about it because I don't think it was
good idea to dump everything in one post.
I'm happy async did that for you. it’s possible to make sync function async
it’s not the case other way round
Is this terminology correct? I thought it is easier to make async functions sync by blocking it, and harder to unblock a sync one. Am I confusing this with something else?On the other hand, you need something like CPS or coroutines to cleanly use an async function in a program mostly using sync APIs.
Of course, if you have the right tools (CPS/coroutines) you can make any async API look sync, as you said.
What the quoted bit boils down to is that any function that returns normally can be converted into a function that calls a callback, but not the other way around.
The main problem I see is that very few libraries handle receiving promises as arguments, probably because there isn't a standard/widely-used implementation of them. So your logic ends up being backwards, e.g you'd chain Mustache.render off of a jQuery success promise, rather than just passing the success promise in.
function sum(a, b) { return a + b }
sum = promised(sum)
var a = defer() // make promise
var b = sum(a, 1)
var b = sum(b, 5)
console.log(b) // eventually prints => 17
a.resolve(11) // fulfill promise
Prints: [object Object]15 var deferred = defer() // make promise
var a = deferred.promise
var b = sum(a, 1)
var c = sum(b, 5)
console.log(c) // eventually prints => 17
deferred.resolve(11) // fulfill promiseI've abandoned it at this point for a very particular reason: operators don't work well in this phrasing. That is, the example case given in the above discussion:
function makeView(templateURI, dataURI) {
var template = readURI(templateURI),
data = readURI(dataURI);
return Mustache.render(template, data);
}
...while it's not bad, also doesn't actually try to "write logic", in a post that's about "write logic, not mechanics."What you really want to do is much more complicated because the example code starts to look ugly:
var userPromise = db.getUser(data.session);
return userPromise.attr("role").equals("admin").then(
render(open("yay.xml"), db.send(data.request)),
render(open("no_permissions.xml"), {})
);
Don't get me wrong, I still like the idea and think that there might be a very promising way to do this sort of work in Node. Certainly I prefer those five lines to always writing: db.getUser(data.session, function (err, user) {
if (err) return callback(err);
if (user.role === "admin") {
db.send(data.request, function (err, reply) {
if (err) return callback(err);
render_file("yay.xml", reply, callback);
});
} else {
render_file("no_permissions.xml", {}, callback);
}
});
But as much as I really don't like that lame error stuff which breaks in a stiff wind, I also really want to be cautious about the exchange of these two lines: if (user.role === "admin") {
userPromise.attr("role").equals("admin").then(
What we really want, I guess, is some sort of mesoscopic system -- we still want operators and familiar control structures from our "micro" world, but we also want lazy evaluations and so on from our "macro" framework. This is not impossible but it is hard to get right. If my "ducky" project moves forward it will probably look something like this: return db.getUser(data.session)(function (user) {
return (user.role === admin) ?
render(open("yay.xml"), db.send(data.request)) :
render(open("no_permissions.xml"), {})
});
You bubble up the lazy constructs with returns, the error handling gets internalized within the lazy constructs, and the "control-method" invocation allows you to build an arbitrary control structure with whatever object you have right now. var auth = promised(function(user, data) {
var isAdmin = user.role === admin
return isAdmin ? render(open("yay.xml"), db.send(data.request))
: render(open("no_permissions.xml"), {})
})
var puser = getUser(data.session)
return auth(user, data)I know that people identify strongly with technical choices[0] (and if you feel uneasy, you're probably the victim of this natural inclination of ours[1]), but I'm tired of the semicolon drama here on HN.
Could we please stop?
--
[0] http://paulgraham.com/identity.html
[1] or did I miss your sarcasm?
Constructive post though, thanks!
return Mustache.render(template, data)
Isn't exactly equivalent to the non-promise one, since now `Mustache.render` is async, is it?Functionally the behavior is the same - you're still going to have to gate an asynchronous stream of actions (make sure steps a, b, c have completed before d, etc.), but with this, you can describe that logic where it matters - renderView().then(attachHandlers).then..., instead of callback nesting hell (attachHandlers would have to be in renderView).
But my hope is that more libraries will be accepting promises in a future so one won't have to do the wrapping of a world around.