1

I have many asynchronous methods to execute and my program flows can change a lot depending on each method return. The logic below is one example. I could not write it in a easy-to-read way using Promises. How would you write it?

Ps: more complex flows are welcome.

Ps2: is_business is a predefined flag where we say whether we are writing a "business user" or a "person user".

begin transaction
update users
if updated
    if is_business
        update_business
        if not updated
            insert business
        end if
    else
        delete business
    end if
else
    if upsert
        insert user
        if is_business
            insert business
        end if
    end if
end if
commit transaction
14
  • I think this is a fair question. Are all of these operations supposedly asynchronous? Commented Aug 9, 2016 at 23:32
  • Yes @mooiamaduck. Actually I'm doing database inserts/updates that are all asynchronous. Commented Aug 9, 2016 at 23:33
  • 2
    There is something wrong with the logic. if is_busines { *...* } else { *delete business* }. Why are you deleting business if its not a business. Likewise, *update business* if not updated *insert business*. You should either update a business or insert one. Commented Aug 9, 2016 at 23:38
  • 1
    You don't need to use any library whatsoever. Up until ES7 introduces the relieving async and await you may implement your simple Promise.coroutine() method with standard ES6 promises / generators and handle your async workflow in a generator function as if it is synchronous. If i can find some time later today I will try to give an example. Commented Aug 10, 2016 at 14:55
  • 1
    @Redu understanding how it works is fine, but I'm not sure why I would implement it myself just to say that I did, when there are options like bluebird's Promise.coroutine or the co library available. Commented Aug 12, 2016 at 16:46

2 Answers 2

4

The nice thing about promises is that they make a simple analogy between synchronous code and asynchronous code. To illustrate (using the Q library):

Synchronous:

var thisReturnsAValue = function() {
  var result = mySynchronousFunction();
  if(result) {
    return getOneValue();
  } else {
    return getAnotherValue();
  }
};

try {
  var value = thisReturnsAValue();
  console.log(value);
} catch(err) {
  console.error(err);
}

Asynchronous:

var Q = require('q');

var thisReturnsAPromiseForAValue = function() {
  return Q.Promise(function() {
    return myAsynchronousFunction().then(function(result) {
      if(result) {
        // Even getOneValue() would work here, because a non-promise
        // value is automatically cast to a pre-resolved promise
        return getOneValueAsynchronously();
      } else {
        return getAnotherValueAsynchronously();
      }
    });
  });
};

thisReturnsAPromiseForAValue().then(function(value) {
  console.log(value);
}, function(err) {
  console.error(err);
});

You just need to get used to the idea that return values are always accessed as arguments to then-callbacks, and that chaining promises equates to composing function calls (f(g(h(x)))) or otherwise executing functions in sequence (var x2 = h(x); var x3 = g(x2);). That's essentially it! Things get a little tricky when you introduce branches, but you can figure out what to do from these first principles. Because then-callbacks accept promises as return values, you can mutate a value you got asynchronously by returning another promise for an asynchronous operation which resolves to a new value based on the old one, and the parent promise will not resolve until the new one resolves! And, of course, you can return these promises from within if-else branches.

The other really nice thing illustrated in the example above is that promises (at least ones that are compliant with Promises/A+) handle exceptions in an equally analogous way. The first error "raised" bypasses the non-error callbacks and bubbles up to the first available error callback, much like a try-catch block.

For what it's worth, I think trying to mimic this behavior using hand-crafted Node.js-style callbacks and the async library is its own special kind of hell :).

Following these guidelines your code would become (assuming all functions are async and return promises):

beginTransaction().then(function() {
  // beginTransaction() has run
  return updateUsers(); // resolves the boolean value `updated`
}).then(function(updated) {
  // updateUsers() has "returned" `updated`
  if(updated) {
    if(isBusiness) {
      return updateBusiness().then(function(updated) {
        if(!updated) {
          return insertBusiness();
        }
        // It's okay if we don't return anything -- it will
        // result in a promise which immediately resolves to
        // `undefined`, which is a no-op, just like a missing
        // else-branch
      });
    } else {
      return deleteBusiness();
    }
  } else {
    if(upsert) {
      return insertUser().then(function() {
        if(isBusiness) {
          return insertBusiness();
        }
      });
    }
  }
}).then(function() {
  return commitTransaction();
}).done(function() {
  console.log('all done!');
}, function(err) {
  console.error(err);
});
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much @mooiamaduck. That is exactly what I expected. I have understood how it works but unfortunately in my opinion it is still hard to read. I found that making a single callback function and working with flags in a switch/case is easier to understand.
I find this more natural, so I suppose it's a matter of taste.
The thing that scares me is that non-promise code needs to be turned into promise code to work with other promise code. This means that as soon as you start using a few promises, they will bleed over your whole codebase until almost everything you write is promise-based. That's not necessarily bad, but it's scary thinking about what happens if you hit a dead-end where for some reason the code simply cannot be promise based for some reason. Maybe this never happens?
2

The solution is a mix of @mooiamaduck answer and @Kevin comment.

Using promises, ES6 generators and co library makes the code much clearer. I found a good example when reading a postgresql node library example (pg). In the example below pool.connect and client.query are asynchronous operations that returns Promises. We can easily add an if/else after geting result and then make more async operations keeping code looking like synchronous.

co(function * () {
  var client = yield pool.connect()
  try {
      yield client.query('BEGIN')
      var result = yield client.query('SELECT $1::text as name', ['foo'])
      yield client.query('INSERT INTO something(name) VALUES($1)', [result.rows[0].name])
      yield client.query('COMMIT')
      client.release()
  } catch(e) {
    // pass truthy value to release to destroy the client
    // instead of returning it to the pool
    // the pool will create a new client next time
    // this will also roll back the transaction within postgres
    client.release(true)
  }
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.