简体   繁体   中英

Question about asynchronous JavaScript with Promise

Here I have a function that takes an array of string that contains the user names of github accounts. And this function is going to return an array of user data after resolving. There should be one fetch request per user. and requests shouldn't wait for each other. So that the data arrives as soon as possible. If there's no such user, the function should return null in the resulting array.

An example for the input would be ["iliakan", "remy", "no.such.users"] , and the expected returned promise after resolving would give us [null, Object, Object] , Object being the data that contained info about a user.

Here is my attempt to solve this question.

function getUsers(names) {
  return new Promise(resolve => {
    const array = [];
    const url = "https://api.github.com/users/";
    const requests = names.map(name => {
      const endpoint = `${url}${name}`;
      return fetch(endpoint);
    });
    Promise.all(requests).then(reponses => {
      reponses.forEach(response => {
        if (response.status === 200) {
          response.json().then(data => {
            array.push(data);
          });
        } else {
          array.push(null);
        }
      });
      resolve(array);
    });
  });
}

It does work, ie returning an array [null, Object, Object] . And I thought it fulfilled the requirements I stated above. However, after looking at it closely, I felt like I couldn't fully make sense of it.

My question is, look at where we resolve this array, it resolved immediately after the forEach loop. One thing I don't understand is, why does it contain all three items when some of the items are pushed into it asynchronously after the json() is finished. what I mean is, in the case where response.status === 200 , the array is pushed with the data resolved from json() , and I would assume this json() operation should take some time. Since we didn't resolve the array after json() operation is finished, how come we still ended up with all data resolved from json() ?

 Promise.all(requests).then(reponses => {
          reponses.forEach(response => {
            if (response.status === 200) {
              response.json().then(data => {
                array.push(data); <--- this should take some time 
              });
            } else {
              array.push(null);
            }
          });
          resolve(array); <--- resolve the array immediately after the `forEach` loop
        });
      });

It looks to me like the array we get should only have one null in it since at the time it is revolved, the .json() should not be finished

You're right, the result is pushed later into the array.

Try to execute this:

const test = await getUsers(['Guerric-P']);
console.log(test.length);

You'll notice it displays 0 . Before the result is pushed into the array, its length is 0. You probably think it works because you click on the array in the console, after the result has arrived.

You should do something like this:

function getUsers(names) {
    const array = [];
    const url = "https://api.github.com/users/";
    const requests = names.map(name => {
            const endpoint = `${url}${name}`;
            return fetch(endpoint);
        });
    return Promise.all(requests).then(responses => Promise.all(responses.map(x => x.status === 200 ? x.json() : null)));
};

You should avoid using the Promise constructor directly. Here, we don't need to use it at all.

 const url = "https://api.github.com/users/"; const getUsers = names => Promise.all(names.map(name => fetch(url + name).then(response => response.status === 200 ? response.json() : null))); getUsers(["iliakan", "remy", "no.such.users"]).then(console.log);

The Promise constructor should only be used when you're creating new kinds of asynchronous tasks. In this case, you don't need to use the Promise constructor because fetch already returns a promise.

You also don't need to maintain an array and push to it because Promise.all resolves to an array. Finally, you don't need to map over the result of Promise.all . You can transform the promises returned by fetch .

The thing is that because json() operation is really quick, especially if response data is small in size it just has the time to execute. Second of all as objects in JavaScript passed by reference and not by value and Array is a object in JavaScript, independently of execution time it'll still push that data to the array even after it was resolved.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM