3

I have a script that sets a set number of usernames, and then for each username, runs a fetch request to twitches API endpoint to get the Json information for each user. I then wanted to put that information into an array for later use, and found out I had to use promises, which I never worked with before. I read through this: https://developers.google.com/web/fundamentals/primers/promises and this: https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md and I read Felix Kling response here: How do I return the response from an asynchronous call? and I'm still having trouble understanding exactly what goes where and what does what. This is what I have so far:

var users = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"]

var arr = [];

var headers = {
  'Client-Id': 'xxxxxxxxxxx'
}

var promise = new Promise(function usersJson(callback) {
  for (i = 0; i < users.length; i++) {
    var url = "https://api.twitch.tv/helix/users?login=" + users[i];
    fetch(url, {headers})
    .then(function(response) {
      return response.json();
    })
    .then(function(myJson) {
      arr.push(myJson);
    })
    callback(arr);
  };
})

promise.then(function(result) {
  console.log(result);
})

and from what I understand, this should be running through each endpoint, pushing the data to my array variable, and then once the whole things is "fulfilled" it should call the 'then' thing with my promise variable to console.log my array. All I get is a blank array and I'm really not sure what I'm doing wrong. If I put the console.log(arr) in after the arr.push(json), I get 8 arrays returned, each with the next response body tacked on, so I know the call is working, I just can't get it to stick in my array.

Cdhippen
  • 499
  • 7
  • 23
  • Why use a for loop when a forEach will do? – evolutionxbox Apr 19 '18 at 00:31
  • You’re also not returning anything in promise. – evolutionxbox Apr 19 '18 at 00:32
  • no, actually your ```callback(arr);``` should be inside the 2nd ```then```, and also each fetch is a promise, so once you resolve the firstone it will trigger the ```callback(arr)``` and will get out – Prince Hernandez Apr 19 '18 at 00:36
  • I just switched it to a forEach, and that doesn't solve my problem. So far as I can tell it doesn't matter whether you use forEach or a for loop. – Cdhippen Apr 19 '18 at 00:36
  • 1
    you're calling `callback(arr);` before the promise executes – Jaromanda X Apr 19 '18 at 00:36
  • I'm confused, so if I put the callback in the second one, then I only get the one thing added to my array. How do I get them all added to the array? – Cdhippen Apr 19 '18 at 00:38
  • @Cdhippen fetch returns a promise for you, so you don't need to wrap the fetch call with a "promise executor", see my answer. Wrap the fetch call in a function, not a promise executor. – Alexander Mills Apr 19 '18 at 00:53

3 Answers3

4

One way that may help you to think more in promises is that once you're in promise-land you can only get the data while you're still in promise-land. In your example, you're getting the data from the fetch promise. That means that you can only use that data from inside of the fetch promise. Notice here:

.then(function(myJson) {
  arr.push(myJson);
})
callback(arr);

The arr.push where you're building the array is inside the promise, but callback(arr) is outside. Since you're not inside the promise anymore you can't get the data.

In your case you also want to wait on multiple promises simultaneously since you're fetching data from multiple sources at once. The best way to do this would be to use Promise.all and an array of promises:

const promise = Promise.all(users.map(user => {
    const url = `https://api.twitch.tv/helix/users?login=${user}`;
    return fetch(url, { headers })
    .then(function(response) {
      return response.json();
    });
}));

Now promise.then will give you access to an array of the data once all the promises have resolved. You don't need to push to another array, the .map will create the array of promises for you.


You can also write this using the async / await syntax which is easier to reason about and potentially cleaner.

const promise = Promise.all(users.map(async user => {
    const url = `https://api.twitch.tv/helix/users?login=${user}`;
    const response = await fetch(url, { headers });
    return response.json();
}));

By the way, you should only need to use new Promise when working with libraries that don't implement promises themselves. fetch already returns a Promise, so when you call it you're already getting a promise back so there's no need to wrap it with new Promise.

Explosion Pills
  • 176,581
  • 46
  • 285
  • 363
  • This is an amazing explanation, thank you very much. I was able to get the console.log working with this information, and I should be able to move forward from here. Much appreciated. – Cdhippen Apr 19 '18 at 02:20
2

To get the result you want, your code can be rewritten as

const users = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"]

const headers = {
  'Client-Id': 'xxxxxxxxxxx'
}

Promise.all(users.map(user => fetch(`https://api.twitch.tv/helix/users?login=${user}`, {headers}).then(response => response.json())))
.then(result => console.log(result));

A little more readable

const users = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"]

const headers = {
  'Client-Id': 'xxxxxxxxxxx'
}

const mapper = user => 
    fetch(`https://api.twitch.tv/helix/users?login=${user}`, {headers})
    .then(response => response.json());

const promises = users.map(mapper);

Promise.all(promises)
.then(result => console.log(result));

and, finally, since your original code shows no knowledge of ES2015+

var users = ["ESL_SC2", "OgamingSC2", "cretetion", "freecodecamp", "storbeck", "habathcx", "RobotCaleb", "noobs2ninjas"];

var headers = {
    'Client-Id': 'xxxxxxxxxxx'
};

var mapper = function mapper(user) {
    return fetch("https://api.twitch.tv/helix/users?login=" + user, { headers: headers }).then(function (response) {
        return response.json();
    });
};

var promises = users.map(mapper);

Promise.all(promises).then(function (result) {
    return console.log(result);
});
Jaromanda X
  • 47,382
  • 4
  • 58
  • 76
0

do something like this instead:

const runTheTrap = async function(users){
   const ret = [];
   for (i = 0; i < users.length; i++) {
    var url = "https://api.twitch.tv/helix/users?login=" + users[i];
    await fetch(url, {headers})
    .then(function(r) {
      return r.json().then(v => ret.push(v));
    });
  };

  return ret;
}


runTheTrap(users).then(function(result) {
  console.log(result);
});

but frankly async/await is kinda 'tarded, I prefer this approach:

     const runTheTrap = function(users){
        return users.map(function(u){
          var url = "https://api.twitch.tv/helix/users?login=" + u;
          return fetch(url, {headers}).then(r => {
              return r.json();
          });
      }



      Promise.all(runTheTrap(users)).then(function(vals){
          // you have your vals
       });
Alexander Mills
  • 1
  • 80
  • 344
  • 642