2

I'm struggling with fetch(), which I'm using to retrieve JSON. I think the problem is that the code completes and tries to 'act' on the array before fetch() has actually downloaded and processed it. I wrote a simpler version initially, which resulted in the same problem; most of the code here is from Google Developers.

As shown below (setTimeout only included to demonstrate the problem) I get different results when accessing the same array twice, a second apart:

dataResultArray = requestJSONResult(searchString);

console.log(dataResultArray);           // logs '[]'
console.log(dataResultArray.length);    // logs '0'

setTimeout(function(){
  console.log(dataResultArray);         // logs the actual JSON array contents
  console.log(dataResultArray.length);  // logs the real array length
}, 1000);


function requestJSONResult(searchString) {
  var tempArray = []

  fetch(`/search/?term=${searchString}`)  
    .then(status)  
    .then(json)  
    .then(function(data) {
      tempArray.push(...data)
    }).catch(function(error) {  
    console.log('Request failed', error);  
    });

  return tempArray;
}

function status(response) {  
  if (response.status >= 200 && response.status < 300) {  
    return Promise.resolve(response)  
  } else {  
    return Promise.reject(new Error(response.statusText))  
  }  
}

function json(response) {  
  return response.json()  
}
Tom M
  • 387
  • 1
  • 3
  • 9
  • google javascript promises and/or callbacks – yBrodsky Aug 15 '17 at 20:24
  • You need to use promises properly. Return the promise from your function and attach a `then` handler to it, instead of trying to push the result into an array. There's no way that will work reliably. –  Aug 15 '17 at 20:25
  • Once you use promises, stick with `then` callbacks, even in your main script that calls these functions. You can only access the asynchronously retrieved data there. – trincot Aug 15 '17 at 20:28
  • But... what is the problem? The first `console.log` logs an empty array because the fetch hasn't returned anything yet. The second `console.log` executes after 1 second, and in your case, by that time the fetch has completed. You should just put `console.log` after `tempArray.push`. The timeout is not necessary (and won't work if the fetch takes longer than 1 second) – Kokodoko Aug 15 '17 at 20:36

2 Answers2

2

It appears you are trying to make your asynchronous code synchronous which is not a good idea.

Just return the promise from fetch to retrieve the data like so:

function requestJSONResult(searchString) {
  return fetch(`/search/?term=${searchString}`)
    .then(status)
    .then(json);
}

function status(response) {
  if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
  } else {
    return Promise.reject(new Error(response.statusText))
  }
}

function json(response) {
  return response.json()
}


requestJSONResult(searchString)
  .then(function(data) {
    console.log(data);
    console.log(data.length);
  })
  .catch(function(error) {
    console.log('Request failed', error);
  });

Now your requestJSONResult returns a promise that could either resolve or reject so any caller can wait for those events.

Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
styfle
  • 22,361
  • 27
  • 86
  • 128
1

Keep in mind that I haven't tested this. You need to use promises throughout. Trying to force asynchronous code synchronous is not reliable, as you've discovered.

function status(response) {  
  if (response.status >= 200 && response.status < 300) {  
    return Promise.resolve(response)  
  } else {  
    return Promise.reject(new Error(response.statusText))  
  }  
}

Notice line 2 of requestJSONResult: return fetch(....). We return the promise from the function.

function requestJSONResult(searchString) {
    return fetch(`/search/?term=${searchString}`)  
        .then(status)  
        .then(function(response) {
            return response.json();
        })  
        .catch(function(error) {  
            console.log('Request failed', error);  
        });
}

requestJSONResult(searchString)
    .then(function(result) { 
        console.log(result); 
    });