Still having troubles with Async Operations

Hello all.

I am currently working on a personal project, and am having trouble with promises in javascript.

My Js looks like this :

$(document).ready(function() {
  console.log('Bonnets');
$('#field').focus();


$('#field').keypress(function(e) {
  var key = (event.keyCode ? event.keyCode : event.which);
  let numbersReactants = [];
  let numbersProducts = [];
  if (key == "13") {
    //Upon enter sign, do an equals check
    var input = $('#field').val();
    if(input.indexOf('=') >= 0) {
      console.log(input);
      var reactants = input.split('=')[0];
      var products = input.split('=')[1];

    //Starts reactants check
    if (reactants.indexOf("+") > 0) {
      reactants = reactants.split("+");
    } else {
      reactants = [reactants];
    }
      for(var i = 0; i<reactants.length; i++) {
        reactants[i] = reactants[i].replace(/\s/g,'');
        if (isNaN(reactants[i][0])) {
          reactants[i] = "1" + reactants[i];
        }
      }
      // Gets numbers



      for (var i = 0; i<reactants.length; i++) {
          $.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[i] + '/', function(result) {
            numbersReactants.push(result);
          });
        }





    //Starts products check
    if (products.indexOf("+") > 0) {
      products = products.split("+");
    } else {
      products = [products];
    }
      for(var i = 0; i<products.length; i++) {
        products[i] = products[i].replace(/\s/g,'');
        if (isNaN(products[i][0])) {
          products[i] = "1" + products[i];
        }
      }

      //Gets products numbers

      for (var i = 0; i<products.length; i++) {
        $.getJSON("https://enthalpy-api.herokuapp.com/" + products[i] + '/', function(result) {
          numbersProducts.push(result);
        });
      }




      //Final Sum
      var productSum = 0;
      for (var i = 0; i<numbersProducts.length; i++) {
        productSum += Number(numbersProducts[i][1]);
      }

      var reactantSum = 0;
      for (var i = 0; i<numbersReactants.length; i++) {
        reactantSum += Number(numbersProducts[i][1]);
      }


      console.log(numbersReactants);
      console.log(numbersProducts);
      console.log("Final Enthalpy: ", (productSum - reactantSum));


      }

    }
});
});

As you can see, and as poined out to me by another user, my $.getJSON calls are async with the final product and reactant sum calls(i.e. the reactant and product sum calls are made before there ever are values in the arrays in which they oversee). Another use who helped me out before recommened I used Promises, more specifically Promise.all because I have a for loop setup for 2 different ajax calls, however I have no idea how to setup a Promise.all in this setting. His advice was helpful but there is still a gap between my understanding. Someone please help.

A structure like this might be used if you want to get all your reactants, all your products, and then do something with all your results.

function reactantOrProductToPromise(reactantOrProduct) {
    return new Promise((resolve, reject) => {
        $.getJSON("https://enthalpy-api.herokuapp.com/" + reactantOrProduct + '/', function (result) {
            resolve(result);
        });
    });
}

var reactantPromises = reactants.map(reactant => reactantOrProductToPromise(reactant));
var productPromises = products.map(product => reactantOrProductToPromise(product));

Promise.all([...reactantPromises, ...productPromises]).then(function() {
    //do something when everything has loaded
});

I do believe jQuery $.getJSON already returns a promise like object, so you can utilize the $.when and .then functions from jQuery

for example this could be a way to sum all reactants

var reactantPromises=[];
for (var i = 0; i<reactants.length; i++) {
  //store all promises to an array
  reactantPromises.push($.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[i] + '/'));
}
//use .apply make each promise an argument in $.when
$.when.apply($,reactantPromises).then(()=>{
  //then work with the responses when everything is resolved. 
  //Responses are passed in as arguments for your callback function, in the form of [data, textStatus, jqXHR]
  reactantResponses=[].slice.call(arguments);
  var sum=0;
  for (var i = 0; i<reactantResponses.length; i++) {
    sum+=Number(reactantResponses[i][1]);
  }
// log sum for example
  console.log(sum);
});

@RadDog25

I tried doing something like this:

      function numberPromise(compound, array) {
        return new Promise(function(resolve, reject) {
          $.getJSON("https://enthalpy-api.herokuapp.com/" + compound + "/", function(data) {
            array.push(data);
          });
        });
      }

      var reactantPromises = reactants.map(function(reactant) {
        numberPromise(reactant, numbersReactants);
      });
      var productPromises = products.map(function(product) {
        numberPromise(product, numbersProducts);
      });

      Promise.all([reactantPromises, productPromises]).then(function() {
      var productSum = 0;
      for (var i = 0; i<numbersProducts.length; i++) {
        productSum += Number(numbersProducts[i][1]);
      }

      var reactantSum = 0;
      for (var i = 0; i<numbersReactants.length; i++) {
        reactantSum += Number(numbersProducts[i][1]);
      }


      console.log(numbersReactants);
      console.log(numbersProducts);
      console.log("Final Enthalpy: ", (productSum - reactantSum));

      });

But I still don’t seem to be getting my results that I wanted. Do you see anywhere that I am going wrong?

@psychometry

That would seem to work if I only cared about validating the completion of one array of $.getJSON calls however, I need to validate the completion of two arrays, and upon the completion of both, execute a mathematical operation. How could I go about doing that?

I don’t want to write the code for you, but I think you can either load both array of into $.when, and find a way to differentiate reactants and products in .then function, or find a way to use chaining .thens

$.when behaves kind of similarly to promise.all

I think you left out the spread operator in this line

Promise.all([reactantPromises, productPromises])
should be
Promise.all([...reactantPromises, ...productPromises])

Well, your numberPromise function returns a promise, but that promise is not resolved. Also your Promise.all() is accepting a 2d array rather than a 1d array. That is why I used the spread operator.

My approach was basically to turn this from a jQuery async problem to an ES6 one (because that is what I know) but as pyschometry suggested there is almost certainly a way to do this with jQuery without needing to use ES6 if your project wasn’t using it before.

All that being said, what are you making here exactly? It seems like an interesting project.

@RadDog25 @psychometry

I’m probably going to see if I can use the jQuery approach to my problem. I’ll review some more documentation and see if I can manage out a solution. The project I am making is an enthalpy calculator that returns the ∆Hº of any chemical equation a user puts in

@psychometry

I’m looking at your sample for jQuery, and for the live of me I don’t understand where the variable reactantResponses comes from or what it does

Let me give you a simpler example

const promiseA=$.getJSON("someurl");
const promiseB=$.getJSON("someotherurl");

$.when(promiseA, promiseB).then(function(){
    console.log(arguments);
});

the responses from promiseA, and promiseB are both sent into the callback function in .then as arguments. As I wrote in the comments, responses from jQuery ajax calls take the form of data, textStaus, jqXHR, for example response of GET https://enthalpy-api.herokuapp.com/Ca(OH)2(s) is

{compound: Ca(OH)2(s), enthalpy: -986.1}, 200, jqXHR object

In the case of the simpler example, 2 promises are resolved, so 2 responses are passed into the callback function as arguments. I also pointed this out in the first code sample.

In JavaScript, you can call a function with more arguments that what is explicitly stated in the function parameter. For example:

function foo(){
    console.log(arguments);
}
foo(a); //prints {0:a}
foo(a,b,c); //prints {0:a,1:b,2:c}

Same thing is happening here in the callback function. If you log the arguments like the simple example, you should see something like this

{
   0:[data,text,jqXHR],
   1:[data,text,jqXHR]
}

What I did is I turned the arguments object into an array with a name because I thought it’d be more readable knowing that it’s an array of responses, and you may fancy using reduce to calculate the sum. That’s what reactantResponses is.

If the code executes, you can simply log it and see what it looks like, but long story short, it’s an array of responses from all the promises loaded into $.when.

@psychometry

This actually makes a ton of sense now. Thank you

@psychometry

I’ve changed some things around. It’s not functioning but I don’t totally know why. I assume its still something with these darn promises. (I’m only console logging it for test, but the problem is the sums render a 0 value)

      // Gets numbers
      function getNumsReact () {
        for (var i = 0; i<reactants.length; i++) {
          $.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[i] + '/', function(result) {
            numbersReactants.push(result);
          });
        }
      }
      //Gets products numbers
      function getNumsProds () {
      for (var i = 0; i<products.length; i++) {
        $.getJSON("https://enthalpy-api.herokuapp.com/" + products[i] + '/', function(result) {
          numbersProducts.push(result);
        });
      }
    }

      var reactantSum = () => {
        sum = 0;
        for(var i = 0; i<numbersReactants.length; i++) {
            sum += numbersreactants[i][1];
        }
        return sum;
      }

      var productSum = () => {
        sum = 0;
        for(var i = 0; i<numbersProducts.length; i++) {
            console.log(numbersProducts[i][1]);
            sum += numbersProducts[i][1];
        }
        return sum;
      }

      $.when(getNumsReact).then(function() {
        console.log(reactantSum());
      });
      $.when(getNumsProds).then(function() {
        console.log(productSum());
      });

      //Final Sum

      console.log(numbersReactants);
      console.log(numbersProducts);

it doesn’t look like you’re using promises at all

$.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[i] + '/', function(result) {
            numbersReactants.push(result);
          })

by doing this, you intend to push response numbersReactant, not the promise. so you’re actually just queuing up a bunch of async operations and their callbacks. I’m surprised $.when works at all given that you gave it a function, not an object.

You noticed in my samples I didn’t give the AJAX call a callback, that’s because I don’t want a callback to handle the response as soon as it gets the data, I want to do it all when I get all the data

@psychometry

I realize that mistake now, I tried something different this time. I moved the promises over to the algorithimic calculations, and then left the jQuery in its own funciton, however this one doesn’t work either. I still don’t really understand what I’m doing.

      // Gets numbers
      function getNumsReact () {
        for (var i = 0; i<reactants.length; i++) {
          $.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[i] + '/', function(result) {
            numbersReactants.push(result);
          });
        }
      }
      //Gets products numbers
      function getNumsProds () {
      for (var i = 0; i<products.length; i++) {
        $.getJSON("https://enthalpy-api.herokuapp.com/" + products[i] + '/', function(result) {
          numbersProducts.push(result);
        });
      }
    }
    getNumsReact();
    getNumsProds();

      let reactantSum = new Promise((resolve, reject) => {
        sum = 0;
        for(var i = 0; i<numbersReactants.length; i++) {
            sum += numbersreactants[i][1];
        }
        resolve(sum);
      });

      let productSum = new Promise((resolve, reject) => {
        sum = 0;
        for(var i = 0; i<numbersProducts.length; i++) {
            console.log(numbersProducts[i][1]);
            sum += numbersProducts[i][1];
        }
        resolve(sum);
      });


      reactantSum.then((sum) => {
        console.log(sum)
      });
      productSum.then((response) => {
        console.log(response)
      });

      //Final Sum

      console.log(numbersReactants);
      console.log(numbersProducts);

I don’t think you quite understand how promises works

Go back and look at both mine and RadDog’s code, do you see what we pushed into arrays and where? compare that to what you pushed into arrays, and you should see what went wrong

or try something like this

var c;
var a=$.getJSON('https://enthalpy-api.herokuapp.com/Ca(OH)2(s)');
var b=$.getJSON('https://enthalpy-api.herokuapp.com/Ca(OH)2(s)', (b)=>{
  console.log("I am callback",b);
  c=b;
});
$.when(a).then((a)=>console.log(a));
$.when(b).then((b)=>console.log('I am then',b));
$.when(c).then((c)=>console.log('I am assigned variable',c));

what you’re doing is c when you should be doing a

You are not being considerate of other people’s effort in helping you learn. You really need to learn how to read and try your very best to understand before you ask questions.

Most people who have tried to help or are still helping you won’t give you the complete solution—because they know that by giving you the solution, they would be doing both you and themselves a disfavour.

That’s also why I only posted a link as a response to this thread because you started two other threads related to this, and a few other ones on other parts of the projects. At this rate everyone may just stop responding and be reluctant to help in the future.

Don’t just copy and paste code that people show you and then come back when things don’t work. The beauty about coding is that, most of the time, you know it’s your fault when things don’t work. Ask yourself why it doesn’t work and test the living daylight out of it first. There are no secrets to figuring out what’s wrong—check your error log and try to find where things break and see if you are getting the responses you expect with this handy little tool:

console.log()

There is no point in even debugging your current code because you haven’t actually taken the time to learn how to work with what everyone has suggested so far. The more you code like this the more you will allow yourself to code like this.

If you are actually a chemist then you should be taking a similar approach to when you do chemistry—for example, like how the common experiment of calculating the heats of reaction between sodium hydroxide and hydrochloric acid with Hess’s Law requires you to break the process down into smaller pieces, solving them bit by bit, and then adding them all back up to get the answer.

What you should be doing right now, instead of asking more questions, is to start a new Pen on CodePen, import jQuery and do this:

const reactants = ["H2O(l)"];

console.log("I expect this to be printed first.")
  
jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
    console.log("I expect this to be printed last: ", result);
});
  
console.log("I expect this to be printed second.")

Then you need to ask yourself whether or not you are comfortable with the output that you see in the log, if so, then you need to remember that jQuery.getJSON() returns jqXHR object. It may look confusing and have nothing to do—but it has been mentioned that it has the property of a Promise and also provided a reference to a while ago. Upon closer inspection of the documentation again, you realise that the jqXHR.then(function( data, textStatus, jqXHR ) {}, function( jqXHR, textStatus, errorThrown ) {}); looks like what @psychometry suggested, so you go back to experiment with it:

jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
  console.log("I expect this to be printed first: ", result);
})
.then(function(data) {
  console.log("I expect this to be printed last: ", data);
});

By this point something that you have been struggling must have clicked, but it still doesn’t look exactly like what everyone has shown you, like the jQuery.when() thing, for example. At this point you remember that @psychometry said that it shouldn’t work if you pass it a function and that it requires an (jqXHR in this case) object. Can I wrap the whole jQuery.getJSON() thing inside jQuery.when(), since it returns an object? You then go experiment with it again:

const reactants = ["H2O(l)"];

jQuery.when(jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
    console.log("I expect this to be printed first: ", result);
}))
.then(function(data) {
    console.log("I expect this to be printed last: ", data);
});

Whoa, that worked! That looks pretty messy though—but that’s okay, because @psychometry has shown me that I can simply assign it to a variable. You then try assigning the response to a variable since it’s simply an object:

const reactants = ["H2O(l)"];

const meowed = jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
    console.log("I expect this to be printed first: ", result);
});

jQuery.when(meowed)
    .then(function(data) {
    console.log("I expect this to be printed last: ", data);
});

That worked great! How about as a function?

const reactants = ["H2O(l)"];

function meow() {
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
        console.log("I expect this to be printed first: ", result);
    });
}

jQuery.when(meow)
    .then(function(data) {
    console.log("I expect this to be printed last: ", data);
});

That doesn’t work and you are printing the entire function in the log. You have no idea what is going on but you decided to push ahead and try different things because you really want to understand this. Since jQuery.when() accepts a jqXHR object, it must mean that nothing is actually getting… returned by the function! You try again:

const reactants = ["H2O(l)"];

function meow() {
    return jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
        console.log("I expect this to be printed first: ", result);
    });
}

jQuery.when(meow)
    .then(function(data) {
    console.log("I expect this to be printed last: ", data);
});

But it still doesn’t work and you are effectively getting the same result with an extra return printed. At this point you either go for a walk and come back to it, or you politely ask someone on the forums. It turns out that you are just missing some brackets—after all, you are calling a function so that it can return a jqXHR object after it’s done:

const reactants = ["H2O(l)"];

function meow() {
    return jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactants[0], function(result) {
        console.log("I expect this to be printed first: ", result);
    });
}

jQuery.when(meow())
    .then(function(data) {
    console.log("I expect this to be printed last: ", data);
});

Now that you have the basics somewhat mastered, you decide to tackle a slightly larger problem—getting multiple enthalpies and make sure that you can still control the order of execution. After getting distracted and learned how to use arrow functions, you then start with this:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];

reactants.forEach((reactant) => {
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
        console.log(result);
    });
});

That worked kind of as expected—you remember that the whole point of this exercise is that you had problems with asynchronous jQuery.getJSON() calls so you logged the response a few times and found out that the results returned are not in the same order as the array supplied. Being careful, you think you will the index of the reactant as well just to be sure:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];

reactants.forEach((reactant, index) => {
    console.log(index);
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
        console.log(result);
    });
});

ARGH, why are the indicies getting printed before any of the enthalpies are returned!? But then you calmed down because you just learned how to use .then() earlier:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];

reactants.forEach((reactant, index) => {
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
        console.log(result);
    })
    .then((data) => {
        console.log(index);
    });
});

It’s actually worse than before and the enthalpies and indicies are actually all jumbled up. You think through it step by step carefully and came to the conclusion all three API calls must be getting executed at different times and whenever the processor has time to get to them—the results are probably also different every time because, well, black magic, maybe the server responses at different times, too. That’s irrelevant, though, because you are convinced that you need some way to control the order that data comes through and that’s the only solution. It then becomes obvious that you can simply log the result within .then():

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];

reactants.forEach((reactant, index) => {
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
        /* console.log(result); */
    })
    .then((data) => {
        console.log(index, data);
    });
});

After checking that the indicies do indeed match with the reactants and enthalpies returned, you then find a way to store that data for use later in exactly the order that they should be. You know that they don’t need to be in that order for heats of reactions to be calculated, but you think that you may need them later for presentational purposes:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];
const reactantData = reactants.map((reactant) => {
    return "";
});

reactants.forEach((reactant, index) => {
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
        /* console.log(result); */
    })
    .then((data) => {
        /* console.log(index, data); */
        reactantData[index] = data;
        console.log(reactantData);
    });
});

Then you realise that it doesn’t actually help the moment you include a console.log() after the for loop to check:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];
const reactantData = reactants.map((reactant) => {
    return "";
});

reactants.forEach((reactant, index) => {
    jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
        /* console.log(result); */
    })
    .then((data) => {
        /* console.log(index, data); */
        reactantData[index] = data;
        console.log(reactantData);
    });
});
  
console.log("Meow!") /* This gets printed first */

You know you need some way to make sure that all three API calls are finished before you do anything else. So with what you know and comfortable with, you try to come up with a way to count the number of requests that have been done before anything else is done. With the guidance of the MDN documentation, so wrap the for loop inside a Promise and make sure that the promise is resolved only when reactantData has been filled:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];
const reactantData = reactants.map((reactant) => {
    return "";
});

new Promise((resolve, reject) => {
    reactants.forEach((reactant, index) => {
        jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
            /* console.log(result); */
        })
        .then((data) => {
            /* console.log(index, data); */
            reactantData[index] = data;
            if (reactantData.indexOf("") === -1) {
                console.log("Meow!")
                resolve("All done!");
            }
        });
    });  
})
.then((resolve) => {
    console.log(resolve);
});

YAY! It works! So you wrap it inside jQuery.when() again and include another one for products:

const reactants = ["H2O(l)", "O2(g)", "H2(g)"];
const products = ["H2O(l)", "O2(g)", "H2(g)"];
const reactantData = reactants.map((reactant) => {
    return "";
});
const productsData = products.map((products) => {
    return "";
});
  
console.log("This should be printed first!");
  
jQuery.when(
    new Promise((resolve, reject) => {
        reactants.forEach((reactant, index) => {
            jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + reactant, (result) => {
                /* console.log(result); */
            })
            .then((data) => {
                /* console.log(index, data); */
                reactantData[index] = data;
                if (reactantData.indexOf("") === -1) {
                  console.log("Reactants!")
                  resolve("All done!");
                }
            });
        });
    }),
    new Promise((resolve, reject) => {
        products.forEach((product, index) => {
            jQuery.getJSON("https://enthalpy-api.herokuapp.com/" + product, (result) => {
                /* console.log(result); */
            })
            .then((data) => {
                /* console.log(index, data); */
                productsData[index] = data;
                if (productsData.indexOf("") === -1) {
                  console.log("Products!")
                  resolve("All done!");
                }
            });
        });
    })
)
.then((resolve) => {
    console.log(resolve)
    console.log(reactantData);
    console.log(productsData)
    /* Do calculations */
});

It’s definitely not the prettiest of code, but at least now you spent the hard work in reading and understanding what’s happening and, hopefully, through experiment—and if you bothered reading all of that and working it through bit by bit, you should now be in a much better position to use one of the more elegant solutions that @honmanyau, @RadDog25, @psychometry suggested.

I feel much better now.

Good luck! o:

1 Like

criticisms aside, I think this should be in the wiki. You really detailed your logic and thinking process working with ajax. I feel like it is rather common for people to struggle with it, and I think if people has gone through what you’ve just done, it’d click for them.

I feel like I have an ok understanding of promises and callbacks, but I’d still look at codes I write and think “am I doing this right? this doesn’t feel right !” ,and labor through a similar process