0

Let's say I have to do some computation, which for example consists of computing A (asynchronously, with callback) and then when A is computed, computing B (also async with callback).

The problem is A is optional, if there is no data for it, I should jump right at computing B.

Currently I use pretty ugly code with doubled B execution, something like this:

if (A)
  computeA(A,() => computeB(B,() => console.log("done.")));
else
  computeB(B,() => console.log("done."));

I am asking to avoid baking up some my solution, which would not be recognized by any JS developer :-) Is there some idiom for it?

greenoldman
  • 16,895
  • 26
  • 119
  • 185
  • Closely related: [if-else flow in promise](http://stackoverflow.com/q/26599798/1048572) – Bergi Jul 19 '16 at 20:02

3 Answers3

2

This can be also done quite easily with promises:

if (A)
   p = A();
else
   p = Promise.resolve(defaultValue)

p.then(B).then(done)

assuming A and B both return promises. If they are traditional callback-acceptors, you have to promisify them.

georg
  • 211,518
  • 52
  • 313
  • 390
  • Great! I was hoping for such form, so thank you very much. – greenoldman Jul 19 '16 at 18:31
  • Cool. It wasn't obvious Promises could be used in the original question. This is a much better answer. – Mulan Jul 19 '16 at 18:37
  • @naomik, why did you delete your answer? It was good! Please, could you put it back -- when you need to press the pedal to the metal, dropping any allocations is a valuable thing to do. – greenoldman Jul 19 '16 at 18:41
1

This is essentially continuation passing style. Because the continuation is just a function, you could assign the chain that always happens to a var and then reuse it in each branch of the if

let next = () => computeB(B,() => console.log("done."))
if (A)
  computeA(A, next)
else
  next()

a tiny continuation monad

Continuations are really fun to work with in JavaScript and they express particular problems in very meaningful ways.

Here's a tiny implementation of the continuation monad cont that allows you to define all sorts of functions you wish to chain in your computation.

when is the crucial function here as it allows you to specify a predicate that conditionally binds another function in the chain.

Below, we will start with a continuation of 0, (cont(0)) then add 1 only if the current continuation's value is less than 3 – subsequent calls to when (lt (3)) (f) will return a continuation of an unaltered value

// cont :: a -> cont a
const cont = x =>
  k => k (x)
  
// when :: (a -> Bool) -> (a -> cont a) -> a -> cont a
const when = p => f => x =>
  p (x) ? f (x) : cont (x)

// contAdd :: Number -> Number -> cont Number
const contAdd = x => y =>
  cont (x + y)
  
// lt :: Number -> Number -> Bool
const lt = x => y =>
  y < x
  
// contLog :: a -> a
const contLog = x =>
  (console.log(x), cont(x))
  
// demo
cont (0)
  (contLog) // => 0
  (when (lt (3)) (contAdd (1)))
  (contLog) // => 1
  (when (lt (3)) (contAdd (1)))
  (contLog) // => 2
  (when (lt (3)) (contAdd (1)))
  (contLog) // => 3
  (when (lt (3)) (contAdd (1)))
  (contLog) // => 3
  (when (lt (3)) (contAdd (1)))
  (contLog) // => 3
  
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Thank you! Is there a way to keep the order of the calls -- A first, then B? Here of course it is easy to see the reversed nature, but in real code it could lead to a surprise. – greenoldman Jul 19 '16 at 18:21
0

One idea is to setup a queue to chain asynchronous calculations using promises like this:

function compute(queue, previousResult) {
    if (!queue.length) return previousResult;
    return new Promise((resolve, reject) => {
        //Get Operation
        var operation = queue.shift();
        //If there is nothing to calculate just resolve the promise
        if (!operation[0]) {
            resolve(0);
        }
        //Execute the computation function and give it the 
        //resolve callback to resolve the promise when the calculations done
        operation[1](operation[0], previousResult || null, resolve);
    }).then(result => {
        //Call the next calculation with result of the previous one
        return compute(queue, result);
    });
}

Then:

var A = 10;

function computeA(a, previous, resolve) {
    $.get('https://httpbin.org/get').then(function() {
        resolve(a*10);
    });
}

var B = 20;

function computeB(b, previous, resolve) {
    resolve(previous + b * 10);
}

var C = 20;

function computeC(c, previous, resolve) {
    resolve(previous + c * 10);
}


compute([
    [A, computeA],
    [B, computeB],
    [C, computeC]
]).then(function(result) {
    console.log(result);
});

Demo:

function compute(queue, previousResult) {
    if (!queue.length) return previousResult;
    return new Promise((resolve, reject) => {
        //Get Operation
        var operation = queue.shift();
        //If there is nothing to calculate just resolve the promise
        if (!operation[0]) {
            resolve(0);
        }
        //Execute the computation function
        operation[1](operation[0], previousResult || null, resolve);
    }).then(result => {
        //Call the next calculation with result of the previous one
        return compute(queue, result);
    });
}

var A = 10;

function computeA(a, previous, resolve) {
    $.get('https://httpbin.org/get').then(function() {
        resolve(a*10);
    });
}

var B = 20;

function computeB(b, previous, resolve) {
    resolve(previous + b * 10);
}

var C = 20;

function computeC(c, previous, resolve) {
    resolve(previous + c * 10);
}


compute([
    [A, computeA],
    [B, computeB],
    [C, computeC]
]).then(function(result) {
    console.log(result);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Ismail RBOUH
  • 10,292
  • 2
  • 24
  • 36