-1

When attempting to use asynchronous calls, the response is undefined rather than returning back the expected response data.

You can see how to run into this issue with any of the following examples:

Fetch

const response = getDataWithFetch();
console.log(response);

function getDataWithFetch() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  fetch(url).then(response => {
    return response.json();
  });
}

Axios

const response = getDataWithAxios();
console.log(response);

function getDataWithAxios() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  axios.get(url).then(response => {
    return response.data;
  });
}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

AJAX / jQuery $.get

const response = getDataWithAjax();
console.log(response);

function getDataWithAjax() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  $.get(url, (response) => {
    return response;
  });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Promise

const response = getDataWithPromise();
console.log(response);

function getDataWithPromise() {
  new Promise(resolve => {
    const response = 'I am a response';
    return resolve(response);
  });
}
Ryan Saunders
  • 359
  • 1
  • 13

1 Answers1

2

Overview

Asynchronous calls, such as HTTP get requests via fetch, axios, AJAX or other asynchronous calls via Promises all behave similarly in that what is returned by the time that the function reaches its last line is not actually the result that you want.

Explanation of Issue

This is because asynchronous calls do not wait for the call to finish executing before executing the next line:

Note that in all the examples below, I will be using fetch but the concept could easily be extended to the other call types.

getDataWithFetch();

function getDataWithFetch() {
  console.log('start of outer function');
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  fetch(url).then(response => {
     console.log('start of async function');
     response.json();
     console.log('end of async function');
  });
  console.log('end of outer function');
}

As you can see via Run code snippet, the outer function starts and ends before the async function starts. What is undefined being returned from getDataWithFetch() because it is hitting the end of the function, and has no return statement. If we change the code to add a return statement, we can see what part of the function call is actually being returned:

const response = getDataWithFetch();
console.log(response);


function getDataWithFetch() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  fetch(url).then(response => {
    return response.json();
  });
  return 'hello world';
}

Now the function isn't returning undefined anymore, but it's not returning the fetch request response; instead, it is returning 'hello world'. This is because the fetch request is not blocking the rest of the getDataWithFetch function from executing. To understand why, we need to understand what fetch is doing behind the scenes.

Promises

Resolve

To do so, we need to understand Javascript Promises. There's a great beginner-friendly read on it here: https://javascript.info/promise-basics, but in a nutshell:

A Promise is an object that, when it finishes the asynchronous work, it will call the "resolve" function. Where "resolve" returns the expected data via resolve(dataToReturn).

So behind the scenes, fetch returns a Promise that will call resolve when the fetched data is retrieved, like so:

// just an example of what fetch somewhat looks like behind the scenes
function fetch(url) {
  return new Promise(resolve => {
    // waiting for response to come back from server
    // maybe doing a bit of additional work
    resolve(responseDataFromServer);
  });
}

With that understanding, we now know that we need to return the call to fetch to actually return the Promise, like so:

const response = getDataWithFetch();
console.log(response);

function getDataWithFetch() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  return fetch(url).then(response => response.json());
}

The console.log is no longer returning undefined, but it's not returning the right response either. That's because getDataWithFetch is now returning the Promise object from fetch. It's time to get the response from resolve.

.then

To get the data passed to the resolve function, we need to use the then method on the returned Promise.

A Promise provides the method then to retrieve the value passed to resolve as such: myReturnedPromise.then(dataToReturn => { /* do something with the data */ });. then will only execute once the dataToReturn is ready to be handled.

Final Solution

So by using the then method, we can get the actual response printed to console:

getDataWithFetch().then(response => {
  console.log(response);
});

function getDataWithFetch() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  return fetch(url).then(response => response.json());
}

And there you have it!

Overly Verbose Solution

If there was too much magic in what made that solution work, you can also see the breakdown of it by explicitly returning a Promise to be fulfilled:

getDataWithFetch().then(response => {
  console.log(response);
});

function getDataWithFetch() {
  return new Promise(resolve => {
    const url = 'https://jsonplaceholder.typicode.com/todos/1';
    return fetch(url).then(response => {
      resolve(response.json()));
      // Note that the `resolve` does not have to be part of the `return`
    }
  });
}

Async and Await Solution

Recent improvements to Javascript have provided us with a more succinct way of handling Promises to make them appear synchronous instead of asynchronous. Note that the following code is equivalent to the above solutions, but much easier to read:

async function getDataWithFetch() {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';
  const response = await fetch(url);
  return response.json();
}

getDataWithFetch().then(response => {
  console.log(response);
});

async and await are clean and powerful alternatives to explicitly dealing with Promises, especially when Promise Chaining is involved. I recommend reading more about them here: https://javascript.info/async-await.

Ryan Saunders
  • 359
  • 1
  • 13