5 ms·
Personally, any solution to callback hell will also need to be a language that supports returning multiple values. The following convention is simply too useful
by ender7 14y ago
Personally, any solution to callback hell will also need to be a language that supports returning multiple values. The following convention is simply too useful to me:
foo.doSomethingAsync(function(err, result) {
if (err) {
...
}
...
});
You can obviously accomplish this with exceptions, but then you have a million try/catch blocks floating around all over the place and the code becomes even harder to read (and more verbose to boot).
- pufuwozu 14y agoThere's a way without exceptions. Use an ErrorT[Promise[A]] monad. I've done this in Scala before and it is so much simpler than the JavaScript convention you pointed out. It allows you to write code like this: val query = "Brian" val result = for { db <- connectToDatabase user <- userFromDatabase(db, query) friends <- friendsFromDatabase(db, user.friends) } yield friends Whenever one of the functions above returns an error, the whole expression is that error. The outcome is either an error (if there was any) or the end value of Set[User]. No need to manually handle an error until you get to the end result.
- saryant 14y agoSimilar to this is the Scala fold convention. An example from the Play! Framework for form submissions: def submit = Action { implicit request => contactForm.bindFromRequest.fold( errors => BadRequest(html.contact.form(errors)), contact => Ok(html.contact.summary(contact)) ) }
- pfraze 14y agoThat's basically what the promises spec for Javascript gives you.
- pufuwozu 14y agoExactly. The promises spec defines the Promise monad. The problem is that JavaScript doesn't have monadic syntax, which would make the code a lot more readable.
- zurn 14y agofoo.doSomethingAsync(function(result) { ... }, function(err) { ... });
- epidemian 14y agoYeah, but try to chain three async functions in that style and it becomes evident what pufuwozu meant when they said monads made the code simpler.
- Groxx 14y agoI don't know, I find this quite readable: foo.doSomethingAsync(function(result) { ... }, { error: function(err) { ... }, afterward: function(then) { ... }, evenLater: function(wat) { ... }}); Which is not meant in any way to imply that there's nothing better :) Just that I see no reason to make the comparison to an abnormally-bad version.
- epidemian 14y agoWhen would the "evenLater" callback be called? Besides, that's still only one function call. When you need to call many async things one after the other (that's what i meant by "chaining"), that nested callback-based code becomes much much convoluted than the analogous "normal" return-based code. With promise monads [1] or some sort of language transformation that lets you write code that looks sequential but then is transformed into chained callbacks, you can basically get rid of all those extra callback functions and their associated complexity (i.e. all the mangled "success" and "error" callbacks from chained async functions) and have code that looks normal function calls one after the other. Notice that pufuwozu's example had three consecutive async calls, but it didn't need to create all the callback functions to pass between the calls. [1]: This is a nice little spec for chainable promises: http://wiki.commonjs.org/wiki/Promises/A http://wiki.commonjs.org/wiki/Promises/A. And here's a post that explains how they are used: https://gist.github.com/3889970 https://gist.github.com/3889970
- eurleif 14y agoDeferreds in Twisted make it easy to chain asynchronous code with error handling together: def parseData(data): return doManyFancyThingsWith(data) def parsedAsyncResult(): return asyncResult().addCallback(parseData) def callback(data): print 'Parsed data:', data def errback(err): print 'Oh no, something went wrong!' parsedAsyncResult().addCallbacks(callback, errback) If an error is raised by either asyncResult() or parseData(), it will be caught by the errback function, even though parsedAsyncResult() didn't explicitly do anything to pass errors along.
- zoowar 14y agoI've always seen this as an odd convention. Why not if (typeof result === Error) {
- unoti 14y agoThere's a very good reason for returning a result as (error_code, resulting_data) instead of just returning a single value of resulting_data and then having the caller check to see if resulting_data is an error. The problem is that if you count on the caller to check on the error value for the data, he might forget to do that. And the code works just fine in testing, and it seems like it works. But in the error case, we merrily continue on and try to pass the error around as if it were data. When we return two values, error and data, then the developer is forced to think about the fact that the result could be an error, and think right then about what should happen. THis is a good place to be thinking about it. Also, the developer may not even know at-a-glance that the function could sometimes return an error, unless consulting the documentation. Or more succinctly: it's not about making the code easier to write (which the version you mentioned does); it's instead about making it easier to do the right thing.