6

This thread explains in detail the difference between sync vs async and possible solutions but I am already using one of the solution & still getting error. I think I hit my ES understanding limit here so I really need help on this issue because I just don't understand why its being lost. Below is the snippet that I am using in nuxt project but it doesn't have anything to do with it because I ported this snippet from backend which is express.

async fetch({store, error}) {
  let series = '', courses = [], album = {}
  store.state.courses.forEach(async course => {
    album = {...course}
    series = course.uri.split('/')[2]
    try {
     const {data: {data}} = await axios.get('http://localhost:3000/luvlyapi/videos', {
      params: {
        series  //? album id
      }
    })
    album['videos'] = data
    courses.push(album)
    console.log('loop', courses)
  } catch (err) {
    error({statusCode: err.statusCode, message: err})
  }
})
console.log({courses})
store.commit('SET_COURSES', courses)

} result

you can see that the array is being pushed but still empty once loop ends.

Lahori
  • 1,071
  • 11
  • 20
  • Please indent your code properly to make its logic and flow more obvious. Your current indentation is very misleading. – jfriend00 Feb 07 '19 at 09:07

2 Answers2

7

.forEach() does not wait for your async callback. It just merrily continues through its loop and you log the value of courses and then sometime later, all the await operations inside your callback finish and you put the values into courses. So, it's a matter of timing where you're looking at the variable before the data has been put in it.

It's worth understanding that await pauses ONLY the local function execution (the closest function scope) which in your case is the .forEach() callback. It doesn't cause anything at a higher level to pause. And, since .forEach() does not even look at what your callback returns, it's certainly not looking at the promise that the async callback returns. So, as soon as you hit the await, your async callback returns a promise. The .forEach() loop sees the callback return and immediately launches the next iteration of the loop even though your await inside the callback has not yet finished.

It's important to understand that await ONLY pauses the local function, not anything above it, not any callers. At the moment your async function hits its first await, that function returns a promise to the outside world. For the outside world to pause also, it has to do something with that promise which .forEach() does not.

A for loop, in the other hand, keeps the await inside your actual function so the whole scope is paused with the await. If you want a loop to actually pause with an await, then use a for loop, not a .forEach() loop.

For example, you could do this:

async fetch({store, error}) {
    let series = '',  courses = [],  album = {};
    for (let course of store.state.courses) {
        album = {...course}
        series = course.uri.split('/')[2]
        try {
            const {data: {data}} = await axios.get('http://localhost:3000/luvlyapi/videos', {
                params: {
                    series //? album id
                }
            })
            album['videos'] = data
            courses.push(album)
            console.log('loop', courses)
        } catch (err) {
            error({
                statusCode: err.statusCode,
                message: err
            })
        }
    }
    console.log({courses})
    store.commit('SET_COURSES', courses)
}

FYI, your original implementation also has a problem with sharing higher level scoped variables among multiple async callbacks that are all in flight at the same time which will cause one to stomp on the other. The solution to that is to either do like my code example shows and serialize the async calls so they aren't in flight at the same time or declare the variables to be local to the async scope so each async callback has its own, separate variables.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • thank you very much for the detail explaination around await & I copied your snippet with one tweak i.e. parenthesis around `for` loop and it works. you saved me. – Lahori Feb 07 '19 at 09:24
  • @Lahori - Yeah, that was an extra `}` left over from the end of your `.forEach()` callback. Fixed. You really don't like semi-colons do you. – jfriend00 Feb 07 '19 at 09:27
0

Try to use Promise.all as follows ( write code from head )

async fetch({ store, error }) {
  let series = '', courses = [], album = {}
  let pr = store.state.courses.map(course => {
    return new Promise(resolve => {
      album = { ...course }
      series = course.uri.split('/')[2]
      try {
        const { data: { data } } = await axios.get('http://localhost:3000/luvlyapi/videos', {
          params: {
            series  //? album id
          }
        })
        album['videos'] = data
        courses.push(album)
        console.log('loop', courses)
        resolve();
      } catch (err) {
        error({ statusCode: err.statusCode, message: err })
      }
    })
  });

  await Promise.all(pr);

  console.log({ courses })
  store.commit('SET_COURSES', courses)
}

The problem is with using await inside forEach - which will not wait... more details here

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • thank you very much for your effort but I try to avoid promises but I will keep it in mind. – Lahori Feb 07 '19 at 09:26