Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using async/await to get data from a callback and return a value only after a promise is resolved

I'm trying to use async/await but I think I'm misunderstanding something critically.

As basically as possible, I am trying to calculate the distance between a list of locations and one designated location using google maps api.

Here is a rough example of what I'm trying to do: https://jsfiddle.net/qu5y69rj/1/

You can see that the result of that function is undefined 3 times instead of what I would expect which would be {distance: "ZERO_RESULTS"} for each call in the case of my contrived example.

getDistance = async (start, end) => {
  const origin = new google.maps.LatLng(start[0], start[1]);
  const final = new google.maps.LatLng(end[0], end[1]);
  const service = new google.maps.DistanceMatrixService();
  let result; //need to return this value!
  await service.getDistanceMatrix(
    {
        origins: [origin],
      destinations: [final],
      travelMode: 'DRIVING'
    }, (response, status) => {
      if(status === 'OK') result = {distance: response.rows[0].elements[0].status}
    }
  )
  return result;
}

Why is result being returned before the promise is resolved? How can I return the value of result only after that promise is resolved? It is my understanding that by telling javascript to await, I'm saying don't move forward until this promise has resolved. Is that incorrect? I'm pretty confused and this has me pulling my hair out. Any help is appreciated.

like image 893
Robbie Milejczak Avatar asked Dec 19 '17 18:12

Robbie Milejczak


People also ask

Can I use async await with promise?

async and await Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

Does async await always return promise?

Async functions The word “async” before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically. So, async ensures that the function returns a promise, and wraps non-promises in it.

Can we use await with callback?

The await keyword is used in an async function to ensure that all promises returned in the async function are synchronized, ie. they wait for each other. Await eliminates the use of callbacks in .

Can you await a function that returns a promise?

The keyword await is used to wait for a Promise. It can only be used inside an async function. This keyword makes JavaScript wait until that promise settles and returns its result.


2 Answers

The service.getDistanceMatrix accepts a callback which means ti most likely doesn't return a promise.

However, async functions expect promises.

As a fix, you can wrap getDistanceMatrix it in a promise (or use another method that does return a promise):

const getDistanceMatrix = (service, data) => new Promise((resolve, reject) => {
  service.getDistanceMatrix(data, (response, status) => {
    if(status === 'OK') {
      resolve(response)
    } else {
      reject(response);
    }
  })
});

getDistance = async (start, end) => {
  const origin = new google.maps.LatLng(start[0], start[1]);
  const final = new google.maps.LatLng(end[0], end[1]);
  const service = new google.maps.DistanceMatrixService();
  const result = await getDistanceMatrix(
    service,
    {
      origins: [origin],
      destinations: [final],
      travelMode: 'DRIVING'
    }
  )
  return {
    distance: result.rows[0].elements[0].status
  };
};
like image 74
nem035 Avatar answered Oct 25 '22 04:10

nem035


There are three ways to do async operations with JavaScript:

  1. Callbacks: A function accepts a callback as its final argument. It returns nothing (undefined), and when the async operation completes, the callback is called.
  2. Promises: A function returns a promise, which resolves to the result of the async operation when it completes.
  3. Async/Await: A function returns a promise, and can use the async keyword to get the values of async operations inside its definition. Whatever is returned using the return keyword will be wrapped in a promise.

Since getDistanceMatrix accepts a callback, it returns nothing. The await keyword as used in the code doesn't need to wait; it immediately gets the undefined value returned by getDistanceMatrix. When the operation completes and the callback is called, the getDistance has long finished executing and returned.

You need to wrap getDistanceMatrix so it returns a promise, make getAllDistance() return a promise as well, and await that promise in your console.log() statement:

const coords = [
  ['-36.22967', '-125.80271'],
  ['54.06395', '54.06395'],
  ['-5.00263', '-137.92806']
];

function getDistance (start, end) {
  const origin = new google.maps.LatLng(start[0], start[1]);
  const final = new google.maps.LatLng(end[0], end[1]);
  const service = new google.maps.DistanceMatrixService();

  return new Promise((resolve, reject) => {
    service.getDistanceMatrix(
    {
        origins: [origin],
      destinations: [final],
      travelMode: 'DRIVING'
    }, (response, status) => {
      if(status === 'OK') {
        resolve({ distance: response.rows[0].elements[0].status });
      } else {
        reject(new Error('Not OK'));
      }
    }
  );
  });
}

function getAllDistance (starts, end) {
  const promisedDistances = starts.map((start) => getDistance(start, end));
  // Promise.all turns an array of promises into a promise
  // that resolves to an array.
  return Promise.all(promisedDistances);
}

getAllDistance(coords, ['-30.23978', '-161.31203'])
  .then(result => { console.log(result); });
like image 41
Eric Avatar answered Oct 25 '22 05:10

Eric