How do you handle if/else with async inside

9,210 views
Skip to first unread message

Dan Milon

unread,
Aug 7, 2012, 12:35:20 PM8/7/12
to nod...@googlegroups.com
I am wondering which are the different patterns to handle cases like


var results
if (cond) {
async1(function (err, res) {
results = res
})
}
else {
async2(function (err, res) {
results = res
})
}
// here need to do something with results.

The problem is obvious, but i cannot see any good way to overcome it.

Mark Volkmann

unread,
Aug 7, 2012, 12:47:50 PM8/7/12
to nod...@googlegroups.com
How about this?

var results;
var fn = cond ? async1 : async2;
fn(function (err, res) {
  results = res;
  // Do something with results here.
});
 
--
R. Mark Volkmann
Object Computing, Inc.

Martin Cooper

unread,
Aug 7, 2012, 12:48:56 PM8/7/12
to nod...@googlegroups.com
It'll depend on the individual case, but what I'd probably do in the
specific case above is:

var asyncFn = cond ? async1 : async2
var results
asyncFn(function (err, res) {
// here need to do something with results.
})

--
Martin Cooper


> --
> Job Board: http://jobs.nodejs.org/
> Posting guidelines:
> https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
> You received this message because you are subscribed to the Google
> Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com
> To unsubscribe from this group, send email to
> nodejs+un...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/nodejs?hl=en?hl=en

Dan Milon

unread,
Aug 7, 2012, 12:44:40 PM8/7/12
to nod...@googlegroups.com
True, that works on this simplified case, but there is more logic,
depending on the cond, etc.

I recall reading a blog post about having async if.
like
if (cond, trueFn, falseFn, doneFn)

That was interesting.

Daniel Rinehart

unread,
Aug 7, 2012, 1:49:40 PM8/7/12
to nod...@googlegroups.com
I've handled this by passing along what to do next to the async function:

var next = function(results) {};
if (cond) {
async1(next);
} else {
async2(next);
}

Easy to turn that into an asyncIf function if you find yourself doing
it frequently.

-- Daniel R. <dan...@neophi.com> [http://danielr.neophi.com/]

Gustavo Machado

unread,
Aug 7, 2012, 2:01:58 PM8/7/12
to nod...@googlegroups.com
Here is one approach which is pretty interesting:
http://joseoncode.com/2012/06/24/messing-with-cps-in-js/

We are currently using iced-coffee-script in a project and I couldn't
be any happier, what you wrote would translate to:

if cod
await trueFn defer(err, result)
else
await falseFn defer(err, result)

#here do whatever you want with err or result.

And if you like the other notation:

await trueFn defer(err, result) if cond
await falseFn defer(err, result) unless cond

#do whatever you want with err or result

Cheers!
Gustavo

Bruno Jouhier

unread,
Aug 7, 2012, 2:44:14 PM8/7/12
to nod...@googlegroups.com
Pattern 1:

  if (cond) results = async1(_);
  else results = async2(_);

Pattern 2:

  results = cond ? async1(_) : async2(_);

See https://github.com/Sage/streamlinejs for details.

Andy

unread,
Aug 7, 2012, 10:44:21 PM8/7/12
to nod...@googlegroups.com
I would personally go with using promises.

var q = require('q');
q.ncall(function() {
   if(cond) {
       return async1(); // this is a promise
   }
   return async2(); // so is this
}).then(function(res) {
   // hooray! one of them finished    
}).error(function(res) {
    // something went wrong!
});

Tim Caswell

unread,
Aug 7, 2012, 10:56:02 PM8/7/12
to nod...@googlegroups.com
If there really are only two functions that have the same callback
signature, then it's super easy taking advantage of named function
value hoisting.

if (cond) async1(onDone);
else async2(onDone);

function onDone(err, result) {
// the function finished
}

But I suspect the question involves more complicated cases in practice.

Thomas Blobaum

unread,
Aug 7, 2012, 11:03:12 PM8/7/12
to nod...@googlegroups.com
I'd recommend as Tim suggested, or with little modification to your
code, something like this.

var results
if (cond) {
async1(function (err, res) {
results = res
next()
})
}
else {
async2(function (err, res) {
results = res
next() // <= or pass in the results?
})
}

function next () {
// here need to do something with results.

}


Thomas Blobaum
https://github.com/tblobaum

Dan Milon

unread,
Aug 8, 2012, 7:13:53 AM8/8/12
to nod...@googlegroups.com
That will work, indeed, but it annoys me that indentation and code
readability gets fucked up. You have to follow code traces in order to
understand the ordering of execution.

I already use async for other cases. Do you find any of its control flow
functions helpful for the if/else problem?

Thanks,
danmilon.

Tim Caswell

unread,
Aug 8, 2012, 9:54:31 AM8/8/12
to nod...@googlegroups.com
On Wed, Aug 8, 2012 at 4:13 AM, Dan Milon <danm...@gmail.com> wrote:
> That will work, indeed, but it annoys me that indentation and code
> readability gets fucked up. You have to follow code traces in order to
> understand the ordering of execution.

Dan, you're not going to get any less indentation than my simple
example. There has to be at least one level of indentation after your
callback no matter what unless you extend the language.

If it makes you feel better you can indent your conditional in one
level as well so that they are both one level deep.

What I was showing is that function statements hoist their value, so
you can call them before the line that defines them is executed. This
means your program can execute in a top-down linear fashion without
ever nesting any more than one level deep for callbacks.

As far as what the async library provides, I have no idea, I've never
used it. It will require at least one function block with a level
of indentation though.

I find using named functions and other clever uses of the language
itself provides just as clear code most the time and with a lot less
abstraction.

Dan Milon

unread,
Aug 8, 2012, 12:00:43 PM8/8/12
to nod...@googlegroups.com
Indeed, you cannot get anything less than +1 indentation.

Thanks for your input!
danmilon.

Bruno Jouhier

unread,
Aug 8, 2012, 12:36:35 PM8/8/12
to nod...@googlegroups.com
Unless you extend the language:

  var results = cond ? async1(_) : async2(_);

There is no free lunch: either you are ready to go with a language extension and you'll be able to reduce the amount of extra code/indentation drastically and get the same readability/maintainability as with good old sync JavaScript, or you stick to JavaScript and you'll have to live with the problem.

You may get some relief from async libraries but you will still have one "function" keyword and an indented function body somewhere in your code every time you need to call an async call to get a value. If you have thin layers of logic, this is viable. If you have thick layers with lots of loops and conditionals, I find it just overwhelming.

Bruno

José F. Romaniello

unread,
Aug 8, 2012, 1:18:56 PM8/8/12
to nod...@googlegroups.com
I wrote a blogpost about this specific topic:

http://joseoncode.com/2012/06/24/messing-with-cps-in-js/ 

for me there are two ideal situations:

  • a language with an specific syntax for asynchronous flows like streamlinejs, icedcoffeescript, f# 
  • or make a Continuation Passing Style version of the  javascript constructs (if, for, while, try/catch) (similar to lisp family languages)
there are also a lot of async libraries I know.

2012/8/7 Dan Milon <danm...@gmail.com>
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to

Bruno Jouhier

unread,
Aug 8, 2012, 5:26:26 PM8/8/12
to nod...@googlegroups.com
Jose,

The continuation-passing-style patterns for conditionals and loops are relatively straightforwards but things get a lot hairier with try/catch and try/finally. But also, what about:

* unary and binary operators: async1(_) <= async2(_)
* lazy operators: async1(_) && async2(_)
* ternary operators: cond ? async1(_) : async2(_)
* async conditions in conditionals (ternary, if) and loops
* call chaining: async1(_).async2(_)
* composition: async1(_, async2(_))
* any combination of the above.

You could write generic continuation-passing-style helpers for all these language constructs but they would not help much with the code bloat and readability. And even if you get there, you still have to worry about lots of other details like trampolining calls so that you don't blow the stack with async calls that sometimes invoke their callback synchronously, etc. If you want to seamlessly and safely use all JS language constructs with async calls, you need a solid language extension (or the fibers library).

Actually, streamline.js started as an attempt to find CPS patterns for all the JS language constructs. The difficulty was to find "composable" patterns, i.e. patterns that can be combined with each other in arbitrary ways. Once I had the right patterns, it was relatively easy (although not completely trivial) to write a transformation algorithm that would automatically apply the patterns.

I described all the code patterns that I used (including try/catch and try/finally) in a tedious blog article: http://bjouhier.wordpress.com/2011/05/24/yield-resume-vs-asynchronous-callbacks/ Of course, you could apply these patterns "manually" without using the streamline compiler but this is clearly error prone and overwhelming.

Bruno
2012/8/7 Dan Milon <danm...@gmail.com>

José F. Romaniello

unread,
Aug 8, 2012, 7:07:34 PM8/8/12
to nod...@googlegroups.com
Bruno, I completely agree with this,

2012/8/8 Bruno Jouhier <bjou...@gmail.com>

Dan Milon

unread,
Aug 8, 2012, 8:53:26 PM8/8/12
to nod...@googlegroups.com
Thats your post that i was referring to ;) I liked the CPS style.

Do you know if harmony is bringing any goodness for these situations?

On 08/08/2012 08:18 PM, José F. Romaniello wrote:
> I wrote a blogpost about this specific topic:
>
> http://joseoncode.com/2012/06/24/messing-with-cps-in-js/
>
> for me there are two ideal situations:
>
> * a language with an specific syntax for asynchronous flows like
> streamlinejs, icedcoffeescript, f#
> * or make a Continuation Passing Style version of the javascript
> constructs (if, for, while, try/catch) (similar to lisp family
> languages)
>
> there are also a lot of async libraries I know.
>
> 2012/8/7 Dan Milon <danm...@gmail.com <mailto:danm...@gmail.com>>
>
> I am wondering which are the different patterns to handle cases like
>
>
> var results
> if (cond) {
> async1(function (err, res) {
> results = res
> })
> }
> else {
> async2(function (err, res) {
> results = res
> })
> }
> // here need to do something with results.
>
> The problem is obvious, but i cannot see any good way to overcome it.
>
> --
> Job Board: http://jobs.nodejs.org/
> Posting guidelines:
> https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
> You received this message because you are subscribed to the Google
> Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com
> <mailto:nod...@googlegroups.com>
> To unsubscribe from this group, send email to
> nodejs+un...@googlegroups.com
> <mailto:nodejs%2Bunsu...@googlegroups.com>
> For more options, visit this group at
> http://groups.google.com/group/nodejs?hl=en?hl=en
>
>
> --
> Job Board: http://jobs.nodejs.org/
> Posting guidelines:
> https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
> You received this message because you are subscribed to the Google
> Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com
> To unsubscribe from this group, send email to
> nodejs+un...@googlegroups.com

Bruno Jouhier

unread,
Aug 9, 2012, 3:14:05 AM8/9/12
to nod...@googlegroups.com
Harmony is bringing generators. Generators make it possible to write async code almost like sync code. For example if/else could be written as

  if (cond) yield async1();
  else yield async2();
  // continuation code goes here

or equivalently as

  cond ? yield async1() : yield async2();
  // continuation here

Yield will play nicely with all the JavaScript constructs (try/finally requires a special hack, though). For example you'll be able to write:

binary operators: yield async1() <= yield async2()
call chaining: yield (yield async1()).async2()

You'll only need two helper functions:
* an invokeAsync helper to invoke callback-style APIs from yield-style functions
* a runAsync helper to invoke a yield-style function from standard callback-style node code.

My only problem with this is that it may be costly with multi-level call stacks (asyncF1 calling asyncF2 calling asyncF3 ...) because you'll get one generator per async stack frame. This won't be as efficient as CPS transforms or fibers, unless the cost of a generator is really low.

More info on this on: http://bjouhier.wordpress.com/2012/05/18/asynchronous-javascript-with-generators-an-experiment/

Bruno
>     <mailto:nodejs%2Bu...@googlegroups.com>
Reply all
Reply to author
Forward
0 new messages