2

If I have only synchronous functions and:

var err = f1();
if (err)
   xxxx
else
   yyyy;

and I need to add another function call which is dependent on the first having completed successfully, I can just append it:

var err = f1(a);
if (err)
   xxxx
else {
   yyyy;

   var err2 = f2(b);
   if (err2)
      ...
   else
      rrr;
}

But when the functions are asynchronous, I would initially have:

f1(a, function(err) {
   if (err)
      xxxx
   else 
      yyy;
});

Adding the second call would require some surgery, lots of braces and parentheses to trace:

f1(a, function(err) {
   if (err)
      xxxx
   else {
      yyy;
      f2(b, function(err) {
        if (err)
          ...
        else
          rrr;
      });
   }
});

Adding a third is even more painful and time consuming. Am I missing out on a smarter way?

Old Geezer
  • 14,854
  • 31
  • 111
  • 198

2 Answers2

1

I prefer the promise pattern.

var firstMethod = function() {
   var promise = new Promise(function(resolve, reject){
      setTimeout(function() {
         console.log('first method completed');
         resolve({data: '123'});
      }, 2000);
   });
   return promise;
};


var secondMethod = function(someStuff) {
   var promise = new Promise(function(resolve, reject){
      setTimeout(function() {
         console.log('second method completed');
         resolve({newData: someStuff.data + ' some more data'});
      }, 2000);
   });
   return promise;
};

var thirdMethod = function(someStuff) {
   var promise = new Promise(function(resolve, reject){
      setTimeout(function() {
         console.log('third method completed');
         resolve({result: someStuff.newData});
      }, 3000);
   });
   return promise;
};

firstMethod()
   .then(secondMethod)
   .then(thirdMethod);

(Copied shamelessly from: https://html5hive.org/how-to-chain-javascript-promises/ )

It scales better, and looks way better than nesting, once you hit 5-6 functions.

This assumes that you want to execute one function after the other, in that order. If you don't care about order of execution, but just want to make sure that all asynchronous calls have finished, I would take a look at then $.deferred pattern (jQuery). This makes sure that if you have N asynchronous functions and you want the N+1 function to execute after those have completed, you have a clean way to do it.

Example:

var name = $.post('/echo/json/', {json:JSON.stringify({'name':"Matt Baker"})});
var lastUpdate = $.post('/echo/json/', {json:JSON.stringify({'lastUpdate':"Hello World"})});

$.when(name, lastUpdate)
    .done(function (nameResponse, lastUpdateResponse) {
        var name = nameResponse[0].name;
        var lastUpdate = lastUpdateResponse[0].lastUpdate;
        $("#render-me").html(name+"'s last update was: "+lastUpdate);
    })
    .fail(function () {
        $("#error").html("an error occured").show();

Copied shamelessly #2 from: http://eng.wealthfront.com/2012/12/04/jquerydeferred-is-most-important-client/

I use the #2nd way for example in UI initialization, when several listboxes, dropdowns and whatever get initialized asynchronously, and want to make sure that the user interface is enabled after all asyncs have finished. Why not do it synchronously you ask? To better utilize the multiple http requests the browser can use. It's faster.

pkExec
  • 1,752
  • 1
  • 20
  • 39
0

If your functions return promises and the environment you're targeting supports async/await, then the code barely needs to change:

var err = await f1(a);
if (err)
   xxxx
else {
   yyyy;

   var err2 = await f2(b);
   if (err2)
      ...
   else
      rrr;
}
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • If your functions return promises, then there's no need to check for errors all the time. Just keep things flat and `throw` if there's a problem. – jib Mar 15 '17 at 05:54
  • 1
    @jib Wrapping everything in `try`/`catch` isn't necessarily flat either, and notably lacks a way to handle success cases without catching exceptions from the handler – Bergi Mar 15 '17 at 06:07