Kavfixnel
Kavfixnel

Reputation: 331

NodeJS await not waiting for HTTP call to finish

I have an array of objects and I am iterating through the array with an async forEach loop and making an HTTP get request with Axios. I tell the compiler to wait for axios to finish before proceeding, but for some reason console.log(data) still runs before console.log(ret)

I think this might be because the forEach loop just gets skipped as soomn as it hits the await and continues, but I don't know how to fix this issue

data.forEach(async (e, i) => {
    let req = `https://api.darksky.net/forecast/7e12d816d7818a03901fa6a72e6802f5/${e.lat},${e.log},${Math.floor(e.start_time / 1000)}?units=si`
    let ret = await axios(req)
    console.log(ret)
    data[i]['weather'] = ret.data.currently.summary
    data[i]['cloudCover'] = ret.data.currently.cloudCover
})

console.log(data)

Here is the output that I see (Note that the first array should theoretically have 'weather' and 'cloudCover' attributes, since they are appended)

[ { start_time: 1548952405372,
    end_time: 1548953096266,
    lat: 59.57644286,
    log: 20.16817143 },
  { start_time: 1548958463054,
    end_time: 1548959597889,
    lat: 59.57644286,
    log: 20.16817143 },
  { start_time: 1548964774667,
    end_time: 1548966048587,
    lat: 59.57644286,
    log: 20.16817143 } ]

{ status: 200,
  statusText: 'OK',
  headers: 
   { date: 'Wed, 10 Jul 2019 02:57:13 GMT',
     'content-type': 'application/json; charset=utf-8',
     'content-length': '10354',
     connection: 'close',
     'x-authentication-time': '705ms',
     'x-forecast-api-calls': '13',
     'cache-control': 'max-age=86400',

Upvotes: 2

Views: 4023

Answers (5)

forEach, in fact, doesn't wait for anything: you've given it an async function so it can schedule a start call for that and immediately move on to the next function because there is nothing to wait for: as an async function, its return value is a Promise, not real data.

If you want to wait until all your async functions are done, then you'll have to use Promise.all:

async runThisStuff() {
  await Promise.all(data.map(async (e, i) => {
    let url = `...`
    let ret = await axios(url);
    console.log(ret)
    data[i]['weather'] = ret.data.currently.summary
    data[i]['cloudCover'] = ret.data.currently.cloudCover
  });

  console.log(data);
}

And if you want to do this in global context, you may not be able to await Promise.all (older browsers, Node, etc. can only await inside an async function), and you'll have to use the older then that Promises offer:

Promise.all(
  data.map(async(...) => { ... })
).then(() => {
  console.log(data)
});

Upvotes: 6

user9560865
user9560865

Reputation:

you can use for of:

const foo = async () => {
  const arr = [1,2,3,4,5,6];
  for (let i of arr) {
  	const response = await // async operations
  }
}

foo();

Upvotes: 0

TheElbowClaps
TheElbowClaps

Reputation: 59

forEach is actually synchronous. It takes a callback function as parameter, which in this case is your ASYNC function. So strictly speaking, all the codes were executed but at a different time than you expected them to. None was skipped.

Async/await is just syntactic sugar for Promise. That means that every line of code after "await" in your loop is only executed when the promised is resolved.

A better way to do this could be promise.All() like others have suggested.

Upvotes: 0

Charlie
Charlie

Reputation: 23778

The forEach method makes multiple function calls without waiting for anything regardless of what goes on inside these functions.

The flow inside the functions are effected by await - but forEach itself is not.

Use for-in loop for synchronous remote requests.

async function makeCalls() {

    console.log('starting');

    for (d in data) {

       let req = `https://api.darksky.net/forecast/7e12d816d7818a03901fa6a72e6802f5/${e.lat},${e.log},${Math.floor(e.start_time / 1000)}?units=si`

       let ret = await axios(req)

       console.log(ret)

       d['weather'] = ret.data.currently.summary
       d['cloudCover'] = ret.data.currently.cloudCover
    }

    console.log('ending');


}

Upvotes: 0

Ozylog
Ozylog

Reputation: 56

All you need to do is using Promise.all and Array.map. it will wait all promises to be done. see below.

const newData = await Promise.all(data.map(async(e, i) => {
    let req = `https://api.darksky.net/forecast/7e12d816d7818a03901fa6a72e6802f5/${e.lat},${e.log},${Math.floor(e.start_time / 1000)}?units=si`;
    let ret = await axios(req);
    console.log(ret);
    e.weather = ret.data.currently.summary;
    e.cloudCover = ret.data.currently.cloudCover;

    return e;
}));

console.log(newData);

Upvotes: 0

Related Questions