Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return the response from an asynchronous call

I have a function foo which makes an asynchronous request. How can I return the response/result from foo?

I am trying to return the value from the callback, as well as assigning the result to a local variable inside the function and returning that one, but none of those ways actually return the response (they all return undefined or whatever the initial value of the variable result is).

Example of an asynchronous function that accepts a callback (using jQuery's ajax function)

function foo() {     var result;      $.ajax({         url: '...',         success: function(response) {             result = response;             // return response; // <- I tried that one as well         }     });      return result; // It always returns `undefined` } 

Example using Node.js:

function foo() {     var result;      fs.readFile("path/to/file", function(err, data) {         result = data;         // return data; // <- I tried that one as well     });      return result; // It always returns `undefined` } 

Example using the then block of a promise:

function foo() {     var result;      fetch(url).then(function(response) {         result = response;         // return response; // <- I tried that one as well     });      return result; // It always returns `undefined` } 
like image 557
Felix Kling Avatar asked Jan 08 '13 17:01

Felix Kling


People also ask

How do I return a response from async?

The A in Ajax stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example, $. ajax returns immediately and the next statement, return result; , is executed before the function you passed as success callback was even called.

Can you return from an async function?

Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise. Note: Even though the return value of an async function behaves as if it's wrapped in a Promise.resolve , they are not equivalent.

How do I return a value from async in node?

To return values from async functions using async-await from function with JavaScript, we return the resolve value of the promise. const getData = async () => { return await axios.

What is asynchronous call back?

Asynchronous callbacks are functions passed to another function that starts executing code in the background. Typically, when the code in the background finishes, the async callback function is called as a way of notifying and passing on data to the callback function that the background task is finished.


2 Answers

→ For a more general explanation of asynchronous behaviour with different examples, see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

→ If you already understand the problem, skip to the possible solutions below.

The problem

The A in Ajax stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example, $.ajax returns immediately and the next statement, return result;, is executed before the function you passed as success callback was even called.

Here is an analogy which hopefully makes the difference between synchronous and asynchronous flow clearer:

Synchronous

Imagine you make a phone call to a friend and ask him to look something up for you. Although it might take a while, you wait on the phone and stare into space, until your friend gives you the answer that you needed.

The same is happening when you make a function call containing "normal" code:

function findItem() {     var item;     while(item_not_found) {         // search     }     return item; }  var item = findItem();  // Do something with item doSomethingElse(); 

Even though findItem might take a long time to execute, any code coming after var item = findItem(); has to wait until the function returns the result.

Asynchronous

You call your friend again for the same reason. But this time you tell him that you are in a hurry and he should call you back on your mobile phone. You hang up, leave the house, and do whatever you planned to do. Once your friend calls you back, you are dealing with the information he gave to you.

That's exactly what's happening when you do an Ajax request.

findItem(function(item) {     // Do something with the item }); doSomethingElse(); 

Instead of waiting for the response, the execution continues immediately and the statement after the Ajax call is executed. To get the response eventually, you provide a function to be called once the response was received, a callback (notice something? call back ?). Any statement coming after that call is executed before the callback is called.


Solution(s)

Embrace the asynchronous nature of JavaScript! While certain asynchronous operations provide synchronous counterparts (so does "Ajax"), it's generally discouraged to use them, especially in a browser context.

Why is it bad do you ask?

JavaScript runs in the UI thread of the browser and any long-running process will lock the UI, making it unresponsive. Additionally, there is an upper limit on the execution time for JavaScript and the browser will ask the user whether to continue the execution or not.

All of this results in a really bad user experience. The user won't be able to tell whether everything is working fine or not. Furthermore, the effect will be worse for users with a slow connection.

In the following we will look at three different solutions that are all building on top of each other:

  • Promises with async/await (ES2017+, available in older browsers if you use a transpiler or regenerator)
  • Callbacks (popular in node)
  • Promises with then() (ES2015+, available in older browsers if you use one of the many promise libraries)

All three are available in current browsers, and node 7+.


ES2017+: Promises with async/await

The ECMAScript version released in 2017 introduced syntax-level support for asynchronous functions. With the help of async and await, you can write asynchronous in a "synchronous style". The code is still asynchronous, but it's easier to read/understand.

async/await builds on top of promises: an async function always returns a promise. await "unwraps" a promise and either result in the value the promise was resolved with or throws an error if the promise was rejected.

Important: You can only use await inside an async function. Right now, top-level await isn't yet supported, so you might have to make an async IIFE (Immediately Invoked Function Expression) to start an async context.

You can read more about async and await on MDN.

Here is an example that elaborates the delay function findItem() above:

// Using 'superagent' which will return a promise. var superagent = require('superagent')  // This is isn't declared as `async` because it already returns a promise function delay() {   // `delay` returns a promise   return new Promise(function(resolve, reject) {     // Only `delay` is able to resolve or reject the promise     setTimeout(function() {       resolve(42); // After 3 seconds, resolve the promise with value 42     }, 3000);   }); }  async function getAllBooks() {   try {     // GET a list of book IDs of the current user     var bookIDs = await superagent.get('/user/books');     // wait for 3 seconds (just for the sake of this example)     await delay();     // GET information about each book     return await superagent.get('/books/ids='+JSON.stringify(bookIDs));   } catch(error) {     // If any of the awaited promises was rejected, this catch block     // would catch the rejection reason     return null;   } }  // Start an IIFE to use `await` at the top level (async function(){   let books = await getAllBooks();   console.log(books); })(); 

Current browser and node versions support async/await. You can also support older environments by transforming your code to ES5 with the help of regenerator (or tools that use regenerator, such as Babel).


Let functions accept callbacks

A callback is when function 1 is passed to function 2. Function 2 can call function 1 whenever it is ready. In the context of an asynchronous process, the callback will be called whenever the asynchronous process is done. Usually, the result is passed to the callback.

In the example of the question, you can make foo accept a callback and use it as success callback. So this

var result = foo(); // Code that depends on 'result' 

becomes

foo(function(result) {     // Code that depends on 'result' }); 

Here we defined the function "inline" but you can pass any function reference:

function myCallback(result) {     // Code that depends on 'result' }  foo(myCallback); 

foo itself is defined as follows:

function foo(callback) {     $.ajax({         // ...         success: callback     }); } 

callback will refer to the function we pass to foo when we call it and we pass it on to success. I.e. once the Ajax request is successful, $.ajax will call callback and pass the response to the callback (which can be referred to with result, since this is how we defined the callback).

You can also process the response before passing it to the callback:

function foo(callback) {     $.ajax({         // ...         success: function(response) {             // For example, filter the response             callback(filtered_response);         }     }); } 

It's easier to write code using callbacks than it may seem. After all, JavaScript in the browser is heavily event-driven (DOM events). Receiving the Ajax response is nothing else but an event. Difficulties could arise when you have to work with third-party code, but most problems can be solved by just thinking through the application flow.


ES2015+: Promises with then()

The Promise API is a new feature of ECMAScript 6 (ES2015), but it has good browser support already. There are also many libraries which implement the standard Promises API and provide additional methods to ease the use and composition of asynchronous functions (e.g., bluebird).

Promises are containers for future values. When the promise receives the value (it is resolved) or when it is canceled (rejected), it notifies all of its "listeners" who want to access this value.

The advantage over plain callbacks is that they allow you to decouple your code and they are easier to compose.

Here is an example of using a promise:

function delay() {   // `delay` returns a promise   return new Promise(function(resolve, reject) {     // Only `delay` is able to resolve or reject the promise     setTimeout(function() {       resolve(42); // After 3 seconds, resolve the promise with value 42     }, 3000);   }); }  delay()   .then(function(v) { // `delay` returns a promise     console.log(v); // Log the value once it is resolved   })   .catch(function(v) {     // Or do something else if it is rejected     // (it would not happen in this example, since `reject` is not called).   });
.as-console-wrapper { max-height: 100% !important; top: 0; }

Applied to our Ajax call we could use promises like this:

function ajax(url) {   return new Promise(function(resolve, reject) {     var xhr = new XMLHttpRequest();     xhr.onload = function() {       resolve(this.responseText);     };     xhr.onerror = reject;     xhr.open('GET', url);     xhr.send();   }); }  ajax("https://jsonplaceholder.typicode.com/todos/1")   .then(function(result) {     console.log(result); // Code depending on result   })   .catch(function() {     // An error occurred   });
.as-console-wrapper { max-height: 100% !important; top: 0; }

Describing all the advantages that promise offer is beyond the scope of this answer, but if you write new code, you should seriously consider them. They provide a great abstraction and separation of your code.

More information about promises: HTML5 rocks - JavaScript Promises.

Side note: jQuery's deferred objects

Deferred objects are jQuery's custom implementation of promises (before the Promise API was standardized). They behave almost like promises but expose a slightly different API.

Every Ajax method of jQuery already returns a "deferred object" (actually a promise of a deferred object) which you can just return from your function:

function ajax() {     return $.ajax(...); }  ajax().done(function(result) {     // Code depending on result }).fail(function() {     // An error occurred }); 

Side note: Promise gotchas

Keep in mind that promises and deferred objects are just containers for a future value, they are not the value itself. For example, suppose you had the following:

function checkPassword() {     return $.ajax({         url: '/password',         data: {             username: $('#username').val(),             password: $('#password').val()         },         type: 'POST',         dataType: 'json'     }); }  if (checkPassword()) {     // Tell the user they're logged in } 

This code misunderstands the above asynchronous issues. Specifically, $.ajax() doesn't freeze the code while it checks the '/password' page on your server - it sends a request to the server and while it waits, it immediately returns a jQuery Ajax Deferred object, not the response from the server. That means the if statement is going to always get this Deferred object, treat it as true, and proceed as though the user is logged in. Not good.

But the fix is easy:

checkPassword() .done(function(r) {     if (r) {         // Tell the user they're logged in     } else {         // Tell the user their password was bad     } }) .fail(function(x) {     // Tell the user something bad happened }); 

Not recommended: Synchronous "Ajax" calls

As I mentioned, some(!) asynchronous operations have synchronous counterparts. I don't advocate their use, but for completeness' sake, here is how you would perform a synchronous call:

Without jQuery

If you directly use a XMLHttpRequest object, pass false as third argument to .open.

jQuery

If you use jQuery, you can set the async option to false. Note that this option is deprecated since jQuery 1.8. You can then either still use a success callback or access the responseText property of the jqXHR object:

function foo() {     var jqXHR = $.ajax({         //...         async: false     });     return jqXHR.responseText; } 

If you use any other jQuery Ajax method, such as $.get, $.getJSON, etc., you have to change it to $.ajax (since you can only pass configuration parameters to $.ajax).

Heads up! It is not possible to make a synchronous JSONP request. JSONP by its very nature is always asynchronous (one more reason to not even consider this option).

like image 161
Felix Kling Avatar answered Sep 21 '22 18:09

Felix Kling


If you're not using jQuery in your code, this answer is for you

Your code should be something along the lines of this:

function foo() {     var httpRequest = new XMLHttpRequest();     httpRequest.open('GET', "/echo/json");     httpRequest.send();     return httpRequest.responseText; }  var result = foo(); // Always ends up being 'undefined' 

Felix Kling did a fine job writing an answer for people using jQuery for AJAX, but I've decided to provide an alternative for people who aren't.

(Note, for those using the new fetch API, Angular or promises I've added another answer below)


What you're facing

This is a short summary of "Explanation of the problem" from the other answer, if you're not sure after reading this, read that.

The A in AJAX stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example, .send returns immediately and the next statement, return result;, is executed before the function you passed as success callback was even called.

This means when you're returning, the listener you've defined did not execute yet, which means the value you're returning has not been defined.

Here is a simple analogy:

function getFive(){     var a;     setTimeout(function(){          a=5;     },10);     return a; } 

(Fiddle)

The value of a returned is undefined since the a=5 part has not executed yet. AJAX acts like this, you're returning the value before the server got the chance to tell your browser what that value is.

One possible solution to this problem is to code re-actively , telling your program what to do when the calculation completed.

function onComplete(a){ // When the code completes, do this     alert(a); }  function getFive(whenDone){     var a;     setTimeout(function(){          a=5;          whenDone(a);     },10); } 

This is called CPS. Basically, we're passing getFive an action to perform when it completes, we're telling our code how to react when an event completes (like our AJAX call, or in this case the timeout).

Usage would be:

getFive(onComplete); 

Which should alert "5" to the screen. (Fiddle).

Possible solutions

There are basically two ways how to solve this:

  1. Make the AJAX call synchronous (let’s call it SJAX).
  2. Restructure your code to work properly with callbacks.

1. Synchronous AJAX - Don't do it!!

As for synchronous AJAX, don't do it! Felix's answer raises some compelling arguments about why it's a bad idea. To sum it up, it'll freeze the user's browser until the server returns the response and create a very bad user experience. Here is another short summary taken from MDN on why:

XMLHttpRequest supports both synchronous and asynchronous communications. In general, however, asynchronous requests should be preferred to synchronous requests for performance reasons.

In short, synchronous requests block the execution of code... ...this can cause serious issues...

If you have to do it, you can pass a flag. Here is how:

var request = new XMLHttpRequest(); request.open('GET', 'yourURL', false);  // `false` makes the request synchronous request.send(null);  if (request.status === 200) {// That's HTTP for 'ok'   console.log(request.responseText); } 

2. Restructure code

Let your function accept a callback. In the example code foo can be made to accept a callback. We'll be telling our code how to react when foo completes.

So:

var result = foo(); // Code that depends on `result` goes here 

Becomes:

foo(function(result) {     // Code that depends on `result` }); 

Here we passed an anonymous function, but we could just as easily pass a reference to an existing function, making it look like:

function myHandler(result) {     // Code that depends on `result` } foo(myHandler); 

For more details on how this sort of callback design is done, check Felix's answer.

Now, let's define foo itself to act accordingly

function foo(callback) {     var httpRequest = new XMLHttpRequest();     httpRequest.onload = function(){ // When the request is loaded        callback(httpRequest.responseText);// We're calling our method     };     httpRequest.open('GET', "/echo/json");     httpRequest.send(); } 

(fiddle)

We have now made our foo function accept an action to run when the AJAX completes successfully. We can extend this further by checking if the response status is not 200 and acting accordingly (create a fail handler and such). Effectively it is solving our issue.

If you're still having a hard time understanding this, read the AJAX getting started guide at MDN.

like image 29
Benjamin Gruenbaum Avatar answered Sep 19 '22 18:09

Benjamin Gruenbaum