• We’re currently investigating an issue related to the forum theme and styling that is impacting page layout and visual formatting. The problem has been identified, and we are actively working on a resolution. There is no impact to user data or functionality, this is strictly a front-end display issue. We’ll post an update once the fix has been deployed. Thanks for your patience while we get this sorted.

Async in JavaScript - need to stuff everything into a callback?

fuzzybabybunny

Moderator<br>Digital & Video Cameras
Moderator
The normal synchronous way of doing things is nice and straightforward.

- A function takes in inputs.
- The function performs an action with those inputs.
- The function ejects out a value (returns a value).
- The value that is ejected can be stored into a variable, used directly in other parts of the synchronous code, etc.

But async doesn't seem to be able to do this.

Let's say that I have a service that I want to sell. The price of the service varies by location.

I have a user:

- Enter in a Zip Code
- Zip Code gets sent to an API while returns a city name.
- I use the city name to run a super huge function that returns a price.
- I then want to use the price in other parts of my synchronous code, and that synchronous code is super long and spans across numerous functions that all depend on the price value.

Code:
var calcPrice = function(city){
    // stuff runs
    return price;
};

// Async Function, taken from http://www.zippopotam.us/
// The "place name" is the city.

var returnLocationInfoByZip = function(zip, callback){
    var client = new XMLHttpRequest();
    var response;
    client.open("GET", "http://api.zippopotam.us/us/" + zip, true);
    client.onreadystatechange = function() {
        if(client.readyState == 4) {
            response = client.responseText;
            callback(JSON.parse(response));
        };
    };
    client.send();
};

var zip = "94043";

var price = returnLocationInfoByZip(zip, function(response){
   calcPrice(response.places[0]["place name"]);
});

// This does not work. I'm going to call this the "dependent processing" part of my code.

functionThatDependsOnPrice(price);
AnotherFunctionThatDependsOnPrice(price);
MoreFunctionsThatDependsOnPrice(price);
EvenMoreFunctionsThatDependOnPrice(price);

// This I THINK would work instead

returnLocationInfoByZip(zip, function(response){
   price = calcPrice( response.places[0]["place name"] );
   functionThatDependsOnPrice(price);
   AnotherFunctionThatDependsOnPrice(price);
   MoreFunctionsThatDependsOnPrice(price);
   EvenMoreFunctionsThatDependOnPrice(price);
});

Stuffing all of that code in the callback is really really ugly and confusing.

I would like to just use the price variable inside of my normal synchronous code. But the value from calcPrice never gets returned and thus never gets stored into the price variable. The value of calcPrice is forever stuck inside the asynchronous branch of my code flow which forces me to do all my other dependent processing inside the asynchronous branch / callback.

So a few questions:

- Is it correct that asynchronous code is never able to return a value back into the synchronous code?

- Is a fat callback pretty much normal?

I could go the Promise route, but if I do that I'm just stuffing all of the dependent processing into my then function... it's a little cleaner looking but it's still nested deep inside other things.
 
You may be confusing flow of control with variable scope. The first thing to note is that as written returnLocationInfoByZip doesn't return anything. It passes the response to the callback, but there is no return statement to set your lvalue 'var price.' So 'price' will remain undefined, or probably 'null.' Not sure of the exact behavior there.

A "fat callback" isn't necessarily the norm, but the point is that you have to do processing that depends on a value when you get the value back. That usually means in the callback. You could store it in something accessible in a broader scope, and then access that value and do something with it somewhere else, but that isn't usually a good idea. For one thing everything that's happening after page load is asynchronous, so you don't get to do any processing until some event happens and triggers some of your code to run, and for another expanding the visibility of variables is usually bad.

Your impulse to avoid fat methods is a good one, but in this case I think the right practice is to handle whatever processing is dependent on the price in the callback where you receive the price.
 
You may be confusing flow of control with variable scope. The first thing to note is that as written returnLocationInfoByZip doesn't return anything. It passes the response to the callback, but there is no return statement to set your lvalue 'var price.' So 'price' will remain undefined, or probably 'null.' Not sure of the exact behavior there.

A "fat callback" isn't necessarily the norm, but the point is that you have to do processing that depends on a value when you get the value back. That usually means in the callback. You could store it in something accessible in a broader scope, and then access that value and do something with it somewhere else, but that isn't usually a good idea. For one thing everything that's happening after page load is asynchronous, so you don't get to do any processing until some event happens and triggers some of your code to run, and for another expanding the visibility of variables is usually bad.

Your impulse to avoid fat methods is a good one, but in this case I think the right practice is to handle whatever processing is dependent on the price in the callback where you receive the price.

Awesome. I guess I'm on the right track.

BTW I managed to figure out a different way of doing it with MeteorJS that preserves linearity. Basically I have a function that gets a response from an API. The response gets stored into something called a reactive Session Variable. The reactiveness of it means that it updates in real time.

Meteor has a function called Tracker.autorun(). It will basically automatically re-run any code inside of it if it detects that a reactive data source inside has changed.

Code:
var calcPrice = function(city){
    // stuff runs
    return price;
};

// Async Function, taken from http://www.zippopotam.us/
// The "place name" is the city.

var storeLocationInfoIntoSessionVariable = function(zip){
    var client = new XMLHttpRequest();
    var response;
    client.open("GET", "http://api.zippopotam.us/us/" + zip, true);
    client.onreadystatechange = function() {
        if(client.readyState == 4) {
            Session.set("locationInfo", response);
        };
    };
    client.send();
};

var zip = "94043";

storeLocationInfoIntoSessionVariable(zip);

// Since all of this is located inside Tracker.autorun, everything inside will run or re-run automatically any time the Session Variable "locationInfo" changes (ie. anytime storeLocationInfoIntoSessionVariable is run with a new zip)

Tracker.autorun( function(){
  var price = calcPrice( Session.get("locationInfo").places[0]["place name"] )
  functionThatDependsOnPrice(price);
  AnotherFunctionThatDependsOnPrice(price);
  MoreFunctionsThatDependsOnPrice(price);
  EvenMoreFunctionsThatDependOnPrice(price);
});

Personally, for me, this code is more linear and easy to read. Tracker.autorun() is like an automatic callback on steroids that continually monitors for data changes.
 
i'm pretty confused by exactly what you are asking in this thread, but as mentioned in other threads, promises will make async code work in a syncronous fashion.

what i THINK you are trying to do would be very easy with promises.

- Enter in a Zip Code
- Zip Code gets sent to an API while returns a city name. (make this function return a promise)
- I use the city name to run a super huge function that returns a price.(call this after the promise is resolved in previous function call)
- I then want to use the price in other parts of my synchronous code, and that synchronous code is super long and spans across numerous functions that all depend on the price value.

i'm also not sure what you mean by "fat" callback. a callback can be as little or as short as you want. if it seems to be a "huge function" then that means you should probably break the function down into multiple functions that are smaller in size.
 
Back
Top