0

I want to add a description property to each object in the locations array that is fetched from Wikimedia API but when I log its value inside the loop it is there, but outside the loop, it gets erased.

I looked for solutions with async/await functions or Promise.all() but it didn't work out.

Is there a way to store the value properly to access it later ??

let locations = [
    {
        latLng: [33.975561111111,28.555830555556],
        name: 'Saint Catherine\'s Monastery',
        searchTerm: 'Saint Catherine\'s Monastery',
        urlSerchTerm: 'Saint%20Catherine\'s%20Monastery'
    },
    {
        latLng: [29.91667,31.2],
        name: 'Bibliotheca Alexandrina',
        searchTerm: 'Bibliotheca Alexandrina',
        urlSerchTerm: 'Bibliotheca%20Alexandrina'
    }
];

async function fetchAsync (site, location) {
    // await response of fetch call
    let response = await fetch(site);
    // only proceed once promise is resolved
    let data = await response.json();
    // only proceed once second promise is resolved
    location.description = data[2][0];

    return location.description;
  }

// let fetches = [];
for (let i = 0; i < locations.length; i++) {
    let site = `https://en.wikipedia.org/w/api.php?action=opensearch&search=${locations[i].urlSerchTerm}&limit=1&namespace=0&format=json&origin=*`;

    fetchAsync(site, locations[i])

}
console.log(locations[1].description)
Hyyan Abo Fakher
  • 3,497
  • 3
  • 21
  • 35
Nouran Awad
  • 33
  • 1
  • 8
  • Where do you actually append the result of `fetchAsync` into the `locations` array? – Cloud_Ratha Sep 05 '18 at 11:24
  • 1
    Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Evert Sep 05 '18 at 11:50
  • @Cloud_Ratha inside the fetchAsync function, the location.description is assigned to the result from the fetch response – Nouran Awad Sep 05 '18 at 11:58

2 Answers2

1

This is simply a timing problem. Your fetch calls are getting executed asynchronously while the console.log(...) statement in the last line of your code snippet is being executed synchronously. In other words, the response to the requests issued by fetch will return after the console.log(...) and the description properties will still be undefined.

You can convince yourself of this by looking at the code below where the console.log(...) statement is wrapped in a timeout. Now the fetched description will be logged instead of undefined.

let locations = [
    {
        latLng: [33.975561111111,28.555830555556],
        name: 'Saint Catherine\'s Monastery',
        searchTerm: 'Saint Catherine\'s Monastery',
        urlSerchTerm: 'Saint%20Catherine\'s%20Monastery'
    },
    {
        latLng: [29.91667,31.2],
        name: 'Bibliotheca Alexandrina',
        searchTerm: 'Bibliotheca Alexandrina',
        urlSerchTerm: 'Bibliotheca%20Alexandrina'
    }
];

async function fetchAsync (site, location) {
    // await response of fetch call
    let response = await fetch(site);
    // only proceed once promise is resolved
    let data = await response.json();
    // only proceed once second promise is resolved
    location.description = data[2][0];

    return location.description;
  }

// let fetches = [];
for (let i = 0; i < locations.length; i++) {
    let site = `https://en.wikipedia.org/w/api.php?action=opensearch&search=${locations[i].urlSerchTerm}&limit=1&namespace=0&format=json&origin=*`;

    fetchAsync(site, locations[i])

}

window.setTimeout(() => {console.log(locations);}, 5000);

You can solve this with Promise.all as suggested by @JeremyThille. This SO answer explains the second use of Promise.all in case this is confusing.

let locations = [
        {
            latLng: [33.975561111111,28.555830555556],
            name: 'Saint Catherine\'s Monastery',
            searchTerm: 'Saint Catherine\'s Monastery',
            urlSerchTerm: 'Saint%20Catherine\'s%20Monastery'
        },
        {
            latLng: [29.91667,31.2],
            name: 'Bibliotheca Alexandrina',
            searchTerm: 'Bibliotheca Alexandrina',
            urlSerchTerm: 'Bibliotheca%20Alexandrina'
        }
    ];


    const fetchDescription = (location) => fetch(`https://en.wikipedia.org/w/api.php?action=opensearch&search=${location.urlSerchTerm}&limit=1&namespace=0&format=json&origin=*`);

    const descriptionRequests = locations.map(fetchDescription);
    Promise.all(descriptionRequests)
    .then(responses => Promise.all(responses.map(r => r.json())))
    .then(descriptions => {
      descriptions.forEach((description, index) => { locations[index].description = description[2][0]; });
    })
    .then(() => {
      console.log(locations);
    });
FK82
  • 4,907
  • 4
  • 29
  • 42
  • Thank you. It worked after adding the setTimeout part around the code snippet that is using the description part. – Nouran Awad Sep 05 '18 at 12:07
  • @NouranSamy You're welcome. Do note that I was using a timeout to illustrate the problem and **not** suggesting that it's a viable solution. You should use `Promise.all` as suggested in @JeremyThille's answer. – FK82 Sep 05 '18 at 13:13
0

Here's my solution with Promise.all :

I'm creating an array of Promises by .mapping over the locations array.

let locations = [
 {
  latLng: [33.975561111111, 28.555830555556],
  name: "Saint Catherine's Monastery",
  searchTerm: "Saint Catherine's Monastery",
  urlSerchTerm: "Saint%20Catherine's%20Monastery"
 },
 {
  latLng: [29.91667, 31.2],
  name: "Bibliotheca Alexandrina",
  searchTerm: "Bibliotheca Alexandrina",
  urlSerchTerm: "Bibliotheca%20Alexandrina"
 }
];

Promise.all(
    locations.map( location => new Promise(async (resolve, reject) => {
        let site = `https://en.wikipedia.org/w/api.php?action=opensearch&search=${location.urlSerchTerm}&limit=1&namespace=0&format=json&origin=*`,
            response = await fetch(site),
            data = await response.json();
        location.description = data[2][0];
        // console.log("Got description = ", location.description)
        resolve();
    })))
.then(() => {
 console.log("locations[1].description = ", locations[1].description);
});
Jeremy Thille
  • 26,047
  • 12
  • 43
  • 63
  • But when adding the console.log statement outside the .then function of the promise, it is undefined. The value is lost outside the promise. – Nouran Awad Sep 05 '18 at 11:56
  • @NouranSamy Yes, because the `console.log(...)` is being executed before the requests return. – FK82 Sep 05 '18 at 11:59