Scope / Async / Await Issues

After watching a few videos I’m trying to work through a problem set.

Original app took loaded stores.js (basic array of stores name and address) then it’d update a new stores array based on async calls to google api for lat long and address. The user then inputs a radius (from their geolocation) and it displays a clickable store list at the bottom of the page. Click and directions are shown.

That part works. The problem I’m having (this is homework) is i’m trying to add on the feature of localStorage; if a user input a radius prior then show those stores below when the page is refresh.

I’m still having trouble understanding async/await as I come from more of a procedural background and just can’t seem to get it to work.

Looking for feedback on the code structure, how would you do it ALL differently. I’ve been trying for about a week now and keep starting over in hopes something will click.

The below is the meat of the code, current issue is when I try to call getStoresWithinRadius() it happens before “allStoresData” is available, I believe.

// setup vars
const GOOGLEAPIKEY = "&key=xxx";
let userLocation, directionsService, directionsDisplay, distanceService, map;
const geocodeUrl =
  antiCors + "https://maps.googleapis.com/maps/api/geocode/json?address=";
const distanceUrl =
  antiCors +
  "https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&origins=";

let allStoresData = [];
let memRadius = localStorage.getItem("radius");
// dom elements
let radius_text = document.getElementById("radius");
let locationRows = document.getElementById("locationRows");
const btnSearch = document.getElementById("btn_search");

btnSearch.addEventListener("click", function() {
  getStoresWithinRadius(radius_text.value);
});

if (memRadius) {
  console.log("memRadius =>", true);
  radius_text.value = memRadius;
}

// show map
// get user location and sort stores by distances
// set map center to user location

function setup() {
  initMap();
  getPosition()
    .then(position => {
      const lat = position.coords.latitude;
      const lng = position.coords.longitude;
      userLocation = {
        lat: lat,
        lng: lng
      };
      return userLocation;
    })
    .then(() => {
      setupStores(userLocation);
    })
    .catch(err => {
      console.error(err.message);
    });
}

function initMap() {
  directionsService = new google.maps.DirectionsService();
  directionsDisplay = new google.maps.DirectionsRenderer();
  distanceService = new google.maps.DistanceMatrixService();
  var mapOptions = {
    zoom: 3,
    center: { lat: 41.8781, lng: -87.6298 }
  };
  map = new google.maps.Map(document.getElementById("map"), mapOptions);
  directionsDisplay.setMap(map);
}

var getPosition = function(options) {
  return new Promise(function(resolve, reject) {
    navigator.geolocation.getCurrentPosition(resolve, reject, options);
  });
};

async function setupStores(userLocation) {
  stores.forEach(async function(store) {
    store = await getStoreLatLong(store);
    store = await getDistanceToStore(store, userLocation);

    // new object array of stores with all data
    allStoresData.push(store);
    allStoresData.sort(sortByDistance);
  });
  return allStoresData;
}

async function getStoreLatLong(store) {
  let geocode = geocodeUrl + store.address + GOOGLEAPIKEY;
  let geoJson = await request(geocode, "json");
  store.coords = geoJson.results[0].geometry.location;
  return store;
}

async function getDistanceToStore(store, userCoords) {
  // convert to url format
  let userLoc = userCoords.lat + "," + userCoords.lng;
  let distance =
    distanceUrl + userLoc + "&destinations=" + store.address + GOOGLEAPIKEY;
  let distJson = await request(distance, "json");
  store.origin = distJson.origin_addresses[0];
  store.distance = distJson.rows[0].elements[0].distance.text;
  store.duration = distJson.rows[0].elements[0].duration.text;
  return store;
}

async function getStoresWithinRadius(radius) {
  let storesInRadius = [];
  allStoresData.forEach(async function(store) {
    let distance = parseFloat(store.distance.replace(/mi|,| /g, ""));

    if (radius >= distance) {
      storesInRadius.push(store);
    } else {
    }
  });
  console.log("Stores in rad => ", storesInRadius);
  return storesInRadius;
}

// clean up distance and parse as float for comparison and reorder
function sortByDistance(a, b) {
  const distA = parseFloat(a.distance.replace(/mi|,| /g, ""));
  const distB = parseFloat(b.distance.replace(/mi|,| /g, ""));

  let comparison = 0;
  if (distA > distB) {
    comparison = 1;
  } else if (distA < distB) {
    comparison = -1;
  }
  return comparison;
}

So if I understand well, you want so save in the localStorage of the browser previous results and later on you want to load the results from the localStorage of the browser and show them on the page?

Yes.
First load radius input is blank
user searches say, 50 miles
shows results and saves 50 miles to localstorage (this part works fine)

when user refreshes/revisits… retrieve localstorage value for radius and proceed as if that was manually entered (automatically showing results).

where is declared that antiCors variable?

it’s just an anti cors url i load from a globals.js as part of index.html, using it for local dev.

As a proof of concept:

  • you can save in the local storage the data retrieved from the promise.
  • Add an event listener to the window something like
window.addEventListener('load', (event) => {
   //First load Check for data in local storage. 
   //If any data then present it on the screen
});

I get that, i added as suggest and looks cleaner. The issue isn’t the loading/retrieving the value.

On a clean page with or with localStorage this works when the button is clicked:

btnSearch.addEventListener("click", function() {
  getStoresWithinRadius(radius_text.value);
});

The issue is when I refresh the page after searching, say 500 miles, the 500 miles shows up in the input just fine. I’m trying to make that automatically call getStoresWithinRadius(radius) if localStorage “radius” value is set.

I know how to do that but the issue isn’t how, it’s “when”.


function getStoresWithinRadius(radius) {
  localStorage.setItem("radius", radius);
  let storesInRadius = [];
  allStoresData.forEach(function(store) {
    let distance = parseFloat(store.distance.replace(/mi|,| /g, ""));

    if (radius >= distance) {
      storesInRadius.push(store);
    } else {
    }
  });
  console.log("Stores in rad => ", storesInRadius);
  return storesInRadius;
}

storesInRadius only contains stores with x miles of user based on input. It’s pulling this from the previous async calls from setupStores (which creates allStoresData). So I think it’s a data availability issue:

Page loads, user radius from local storage loads, getStoresWithinRadius(radius) executes based on that but “storesInRadius” hasn’t processed yet.

I hope this makes sense. I’m open to trying anything. Didn’t want to put all my functions in one main function as I don’t feel i’d learn as much this way.

Please create a https://codepen.io and send it to my in private because I am unable to see what is going on from the code posted here.

If you not happy to share the code try to use Promise.all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all .

If is the case where a promise resolves and returns data and other are not yet resolved, promise all will help you to overpass this issue. Promise.all is waiting for all the promises to be resolved and then you got the data to work with in a thenable.

.then((data)=>{
 // all the data 
}).catch((e)=>{
  console.log(e);
});

I work with blue bird when I have to deal with promises I find it easy as has extra stuff.

I can’t message you?

I understand what you’ve posted.

The issue with that is getStoresWithinRadius(radius) is called on two occaision, when user clicks “Search” and when user refreshed the page and has a radius stored.

afaik thenables aren’t really callable in that nature?

  1. Replace window.addEventListener with this:
window.addEventListener('load', () => {
    let newRadius = localStorage.getItem("radius");
    if (newRadius) {
        radius_text.value = newRadius;
        var newData = JSON.parse(localStorage.getItem(String(newRadius)));
        if (newData) {
            newData.map((el) => {
                displayStores(el);
            });
        }
    } 
    setup();
});

  1. Remove onload=setup() from body element located in the html file.
  2. Remove getStoresWithinRadius(radius) from setup function.
  3. Replace getStoresWithinRadius function with this.
function getStoresWithinRadius(radius) {
  locationRows.innerHTML = "";
  let storesInRadius = [];
  allStoresData.forEach(function(store) {
    let distance = parseFloat(store.distance.replace(/mi|,| /g, ""));

    if (radius >= distance) {
      storesInRadius.push(store);
      displayStores(store);
    } else {
    }
  });
  console.log("Stores in rad => ", storesInRadius);
  localStorage.setItem(radius, JSON.stringify(storesInRadius));
  return storesInRadius;
}

Done.

ridiculously helpful! Now to review what you changed and figure out why I’m an idiot! I can’t thank you enough. Works like a charm!

Is this storing a json object with radius value?

localStorage
Storage {radius: "50", 50: "50", length: 2}
localStorage.getItem(50)
"[{"id":2,"name":"Smart Homes - Store 2","a....

If so why doesn’t the json object not show up in localStorage, what is this sorcery!?

:))) look again on your local storage. That line of code is using the radius as a name(key) and the array of objects as a value.

On click you save radius and the last loaded values.

Basically you will end up in local storage with two entries. For example:
One for radius “radius”= 5555;
Another one for values “5555” = [{value: 1}, {value: 2 } … and so on].

On load later on, if you got the radius and if there is a collection equal with radius you render on the screen that collection.

On next click you save input again:

One for radius “radius”= 1000;
Another one for values “1000” = [{value: 555}, {value:333 } … and so on].

Makes sense?

1 Like

And another example:

wow. this seems to be way better than how i was going about it. I’m surprised I got this far with my limited understanding. Everything was all good until localStorage was added to requirements.

Again thank you so much for your time and efforts. I learned a lot! Now to figure out how to just write cleaner lol.