0

Basically I have a function which loops through all the elements from an api and pushes it to a array and then I return the array, but I am getting random of amount of elements everytime I run this function.

function getElements() {
    const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber=";
    var elements = [];
    for(var i=1;i<=118;i++){
        newurl = url+i.toString();
        fetch(newurl)
        .then(response => response.json())
        .then(data => elements.push(data.symbol))
    }
    return elements;
}
console.log(getElements());

thanks in advance

ulou
  • 5,542
  • 5
  • 37
  • 47
Amey patil
  • 25
  • 1
  • 4
  • `how to use async/await` you are asking the right question, you are on the right track. Have you tried it? – Jeremy Thille Jun 04 '21 at 08:12
  • well i did learn about it but i dont how to implement them in the loop – Amey patil Jun 04 '21 at 08:14
  • 1
    BTW it's weird that you get a "random of amount of elements" in your array. According to this code, you should get exactly 0 element, because you return the array immediately, before it is filled by your... 118 concurrent asynchronous calls – Jeremy Thille Jun 04 '21 at 08:14
  • i have no idea also i am javascript beginner so i dont know much about async and await – Amey patil Jun 04 '21 at 08:15
  • You did learn about it, so give it a try :) If you did learn about it, surely you can try and start to write something? I don't want to simply give you the solution on a plate, because I believe you are nearly there and you can do it with what you already know. – Jeremy Thille Jun 04 '21 at 08:16
  • u can try the code ans see for yourself – Amey patil Jun 04 '21 at 08:16
  • Hello there is lot of documentation and question about it, please do your search before asking a question. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function – Nicolas Menettrier Jun 04 '21 at 08:16
  • ok i will try it out thank you so muhc for the reply – Amey patil Jun 04 '21 at 08:16
  • I did try the code, and as expected it gives me exactly `[]`. It can't be otherwise, because synchronous code executes before asynchronous code. – Jeremy Thille Jun 04 '21 at 08:17
  • You are missing 2 keywords: `async` and `await` – ulou Jun 04 '21 at 08:18
  • Yes and removing `.then()` because that's what is messing up with your result – Jeremy Thille Jun 04 '21 at 08:18

6 Answers6

1

You need to push entire promise to array and wait for them all to be resolved, so they will be resolved in parallel.

function getElements() {
    const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber="
    const elements = []
    for(let i=1;i <= 118; i++){
        newurl = url + i // no need to 'i.toString()'
        elements.push( // push entire promise, not just "unpacked" response
          fetch(newurl)
            .then(response => response.json())
            .then(data => data.symbol)
        )
    }
    return Promise.all(elements)
}

getElements().then(elements => {
  console.log(elements.length)
  console.log(elements)
})
.as-console-wrapper { max-height: 100% !important; top: 0; }
ulou
  • 5,542
  • 5
  • 37
  • 47
  • yesh i tried this and got this working but it take a hell lot of time to complete is there a way i can make it take less time ? – Amey patil Jun 04 '21 at 08:22
  • Yurgh no, this is ugly. You are mixing the `async/await` and the `.then()` syntaxes. It eventually works, so I won't downvote, but man is this inelegant – Jeremy Thille Jun 04 '21 at 08:22
  • Yes, do not send 118 requests in a loop. – ulou Jun 04 '21 at 08:22
  • Or don't await each request and send them parallel? –  Jun 04 '21 at 08:23
  • then what is the correct way to do this ? – Amey patil Jun 04 '21 at 08:23
  • Ok, give me 2 mins. I just wanted you to show how to make your code works, not to CR and improve. – ulou Jun 04 '21 at 08:25
  • @Amey patil, send request in batches, like wait for 10 promise to finish, then next 10, so on... – Nur Jun 04 '21 at 08:29
  • 1
    `it take a hell lot of time to complete is there a way i can make it take less time ?` first, don't make 118 concurrent calls, give the server some time to breathe. Then, the results will always be the same (you are querying a periodic table) so do this once, store the results in a variable and reuse them. – Jeremy Thille Jun 04 '21 at 08:32
  • @Ameypatil now it should work fine, but as Jeremy said, it's constant date that won't change, you can simply store it in your app. Also I'm pretty sure there is a way to do it in 1 request instead of 118 (look at the doc). – ulou Jun 04 '21 at 08:38
1

All the promises stored in elements will resolve together using Promise.all() method.

function getElements() {
    const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber=";
    var elements = [];
    for(var i=1;i<=118;i++){
        newurl = url+i.toString();
        const res = fetch(newurl)
        .then(response => response.json());
        elements.push(res);
    }
    return Promise.all(elements);
}
1

Do not send so many request at a same time, send request in batches... Wait for 10 promise to finish, then next 10, then next 10, so on...

async function getElements() {
    const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber=";
    let elements = [], pending = [];
    for(let i=1; i <= 118; i++){
        let p = fetch(url + i)
           .then(response => response.json())
           .then(data => elements.push(data.symbol))
           .catch(console.log);
         
        if (pending.push(p) > 10) {
            await Promise.all(pending);
            pending = [];
        }
    }
    return Promise.all(pending).then(() => elements);
}

// you cannot use async await in global scope, so I used 'then' here instead
getElements().then(elements => console.log(elements.length))
Nur
  • 2,361
  • 2
  • 16
  • 34
0

Ok, here you go then (I have limited the number of calls to 3, to not hammer this poor server too much)

const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber=";

const getElements = async () => {
    let elements = [];

    for(var i=1;i<=3;i++){
        newurl = url+i.toString();

        const response = await fetch(newurl);
        const data = await response.json()
        
        elements.push(data.symbol)
    }
    return elements; // This is a Promise of array, not an array, because an async function always returns a Promise of something. So this can be awaited.
};

(async () => {
    console.log(await getElements());
})();
Jeremy Thille
  • 26,047
  • 12
  • 43
  • 63
0

You can send all requests at once and store the promises in an array. Then you can wait for all promises to resolve:

async function getElements() {
    const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber=";
    const promises = [];
    for(var i=1;i<=118;i++){
        newurl = url+i.toString();
        promises.push(fetch(newurl));
    }
    return Promise.all(promises);
}
(async () => {
    const responses = await getElements();
    responses.forEach(async response => console.log(await response.json()));
})();

A server should be able to handle 118 requests at once but you shouldn't send too many requests.

Be aware that in this minimal example the order of the console.log usually is not the order you sent the requests. forEach doesn't consider awaits.

  • `await` doesn't work inside array methods like `.forEach` or `.map`. – Jeremy Thille Jun 04 '21 at 08:33
  • @JeremyThille Of course, it works. Press "Run code snippet" and see how it awaits `response.json()`. –  Jun 04 '21 at 08:34
  • [No it doesn't](https://codepen.io/jeremythille/pen/ExWXjKa). – Jeremy Thille Jun 04 '21 at 08:36
  • @JeremyThille When I press "Run code snippet" I get 118 objects. It may not work on your platform for some reasons but usually it works. jsfiddle supports it: see https://jsfiddle.net/nzh4d1e3/ and wait about 5 seconds –  Jun 04 '21 at 08:37
  • It works but doesn't do what you think it does. https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop – Jeremy Thille Jun 04 '21 at 08:39
  • @JeremyThille It does exactly what I think and I mentioned it in my answer in the last paragraph. You could use Array#map and some other logic to keep the order. It's just a simple example. –  Jun 04 '21 at 08:39
0

The minimal rule of using async and await is that await can be used in an async function. So if you don't care about parallelism, you can just wrap your entire test into a bigger async function, add the keywords here and there, and that's it:

async function main() {                  // <-- wrapper
    async function getElements() {       // <-- became async, so can use await
        const url = "https://neelpatel05.pythonanywhere.com/element/atomicnumber?atomicnumber=";
        var elements = [];
        for(var i=1;i<=118;i++){
            newurl = url+i.toString();
            await fetch(newurl)          // <-- await request right in the loop
            .then(response => response.json())
            .then(data => elements.push(data.symbol))
        }
        return elements;
    }
    console.log(await getElements());    // <-- await the async function
}
main();                                  // <-- call wrapper
console.log("! results will come after this line !"); // memo about the test still being async
tevemadar
  • 12,389
  • 3
  • 21
  • 49