3

My JavaScript code contains quite a few asynchronous functions. For example, I'm using D3.JS to read a CSV file, and I'm connecting to the Google Maps API to find the driving directions between two addresses.

I'm using this answer on StackOverflow to wait until the asynchronous function is complete (to avoid return variables with a undefined value). However, because I have a lot of asynchronous functions, I have a lot of nested anonymous callback functions:

    carDirections(from, to).done(function(car) {
        transitDirections(from, to).done(function(train) {
        // carDirections and trainDirections have similar anonymous callback functions.

    function carDirections(from, to) {
        var dfd = $.Deferred();

        var directionsService = new google.maps.DirectionsService;

        directionsService.route({
            origin: from,
            destination: to,
            travelMode: google.maps.TravelMode.DRIVING
        }, function(response, status) {
            if (status === google.maps.DirectionsStatus.OK) {
                dfd.resolve(response.routes[0].legs[0]);
            }
        });

        return dfd.promise();
    }
    // Similar code for transitDirections.

This results in a spaghetti of done and Deferred functions, which makes the code very hard to understand. Is there a proper way to solve this? E.g., could I re-program my Google Maps functions to make them synchrounous, or is there another (easier) way to only continue executing code when the previous function has returned a variable?

Community
  • 1
  • 1
TheLeonKing
  • 3,501
  • 7
  • 32
  • 44

2 Answers2

1

You might want to read up some more on the subject of Promises. You can chain asynchronous operations like

asyncOp1()
  .then(result1 => asyncOp2(result1))
  .then(result2 => asyncOp3(result2))
  .then(result3 => Promise.all(asyncOp4(result3), asyncOp5(result3)))
  .catch(error => { /* deal with an error */ })
  // ...

if all of the operations return a Promise (this helps you avoid nesting). (Transpile the above pseudo-code to ES5 and polyfill the Promise if you need to.)


If you're looking for different ways to deal with complex asynchronous code alltogether, you check out RxJS ... not that I'm taking any responsibility if you do :)

hon2a
  • 7,006
  • 5
  • 41
  • 55
  • That seems like a nice solution, thanks! However, if I have the following code: `carDirections(from, to).then(car => trainDirections(from, to)).then(train => console.log(car));`, the variable `car` isn't accessible anymore. Is there a way to keep passing this variable on, other than sending it along as a function argument? – TheLeonKing Jan 07 '16 at 11:20
  • You could save to an outer scope variable or extend the `Promise` monad to collect all of the results in the chain, but it's easiest and most transparent to just send whatever you need along. If you don't want to pollute your train op function, you can always do`carOp().then(car => trainOp(car).then(train => ({ car, train }))).then(({ car, train }) => ...)`. – hon2a Jan 07 '16 at 11:27
0

As far as I see the trainDirections does't need to get any response from carDirections you just need to fire a callback when all of the actions are finished?

You could use deferred objects.

var deferredObj1 = $.Deferred(),
    deferredObj2 = $.Deferred();

$.when(deferredObj1,deferredObj2).done(function(){
    console.log("They are both done!");
});

// inside the Event1 handler:
deferredObj1.resolve();

// inside the Event2 handler:
deferredObj2.resolve();

The Deferred object, introduced in jQuery 1.5, is a chainable utility object created by calling the jQuery.Deferred() method. It can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

Ales Maticic
  • 1,895
  • 3
  • 13
  • 27