asanas
asanas

Reputation: 4280

asynchronous callback in javascript

Problem : can't assign returned value from asynchronous function to mydata global variable.

I have a function which is calling another function to get and parse some data. However, I am not acble to access the data received outside the function.

var mydata = loadJSON( (response) => {
  // Parse JSON string into object
  var actual_JSON = JSON.parse(response);
  console.log(actual_JSON);
  return actual_JSON;
});
console.log('mydata', mydata);

function loadJSON(callback) {
  var xobj = new XMLHttpRequest();
  xobj.overrideMimeType("application/json");
  xobj.open('GET', './my-data.json', true);
  xobj.onreadystatechange = function () {
    if (xobj.readyState == 4 && xobj.status == "200") {
      // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
      callback(xobj.responseText);
    }
  };
  xobj.send(null);
}

As you can see in the above code, mydata is undefined. How can I have the data of actual_JSON accessible outside for use by other javascript functions?

Upvotes: 1

Views: 1201

Answers (3)

AL-zami
AL-zami

Reputation: 9066

Why you are facing this problem ?

To understand the root of the problem, you need to understand the concept of call-stack and async execution in javascript. Here i am giving you a brief introduction but enough to understand your problem

async execution in javascript happens outside of the current call stack. You are calling a function called loadJson which accepts a callback and the callback is executed inside an async handler called onreadystatechange. The status of the call stack in different stage of the execution has been illustrated below :

First, call stack is empty.

   call stack
  -         -    async handler
  -         -    -------------
  -         -      -empty-
  -         -
  - - - - - -

Then loadjson is called and it's pushed into the call stack.

   call stack
  -         -    async handler
  -         -    -------------
  -         -      -empty-
  -         -
  - loadJson-
  - - - - - -

loadjosn executes XMLHttpRequest which is an async code.Async code executes outside of the call stack, somewhere we may call it async-handler here, and it waits there until the async execution is finished. At this point the loadjson function is done with and pushed out of the call stack.So after this call stack is empty again.

  call stack
  -         -    async handler
  -         -    -------------
  -  empty  -    xhrHttpRequest ( wait until ajax finish fetching data )
  -         -
  - - - - - -

When the async xmlhttprequest finished fetching data. the callback function is pushed into the callstack.

   call stack
  -         -    async handler
  -         -    -------------
  -         -    empty ( async execution finished execution)
  -callback -     
  - - - - - -

By this time the callback function forgets that it was a callback for the laodJson function, because it's not called from inside loadjosn function rather it's called from a async handler. So the callback function executes like a separate , standalone function.It may appear from the code that the callback is called from loadjson , but at this point callback has no ties with loadjson function. it a result of an async execution.So invocation of callback has no ties with loadjson anymore.

that's why you can not return any result from callback or loadjosn that will populate the global variable mydata.

How you can solve it ?

you can follow many ways. You can use the old-school continuation-passing-style , or you can use new es6 features like Promises and async-await. They way you are doing it is called is continuation passing style. I assume you are already familiar with this style , so here i only providing the promise and async-await style for your convinience.

Promise way :

var mydata ;

loadJSON( (response) => {
   // Parse JSON string into object
     var actual_JSON = JSON.parse(response);
     console.log(actual_JSON);
     return actual_JSON;
  }).then(response =>{ mydata = response;console.log(mydata)})


  function loadJSON(callback) {
     return new Promise(function (resolve,reject){
     var xobj = new XMLHttpRequest();
     xobj.overrideMimeType("application/json");
     xobj.open('GET', './my-data.json', true);
     xobj.onreadystatechange = function () {
           if (xobj.readyState == 4 && xobj.status == "200") {
             // Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
             resolve(callback(xobj.responseText));
           }
     };
     xobj.send(null);
  })
  }

Async-Await procedure :

function loadJson(){
   return new Promise(function(resolve,reject){
       var xobj = new XMLHttpRequest()

       xobj.onreadystatechange = function(){
           if(xobj.readyState == 4 && xobj.status == 200){
              resolve(xobj.response)
           }
       }

       xobj.open('GET', './my-data.json', true);
       xobj.send()
   })

}

async function askForJson(){
   var data = await loadJson()
   return data
}

askForJson().then(data=>console.log(data))

Upvotes: 1

nareddyt
nareddyt

Reputation: 1085

The root cause is that loadJSON is an asynchronous function. I suggest you read this stackoverflow question to understand the different between synchronous and asynchronous functions.

Since you call loadJSON asynchronously, you register a callback that defines what happens when loadJSON finishes. In your case, you are parsing the json and logging it in your callback.

Keep in mind that since loadJSON is asynchronous, it will kick off the function but will "skip ahead" to the next section of code without finishing. As discussed in the other question above, asynchronous functions don't return anything. Instead, they call the defined callback when they finish. So returning inside the callback and storing the result in mydata does nothing. You need to use the data directly in the callback.

How can you fix this? Simple:

loadJSON( (response) => {
     // Parse JSON string into object
     var actual_JSON = JSON.parse(response);
     console.log(actual_JSON);

     // Place the rest of your code here, here's an example
     console.log('I have the JSON here, I can use it now');
     var my_name = actual_JSON.name;
     console.log('My name is', my_name);
  });

Upvotes: 1

ashkufaraz
ashkufaraz

Reputation: 5297

set Asynchronous to false

 xobj.open('GET', './my-data.json', false);

By sending asynchronously, the JavaScript does not have to wait for the server response

Upvotes: 1

Related Questions