1

I'm stuck on an issue where I'm parsing the results from an API in getSubscriptions(). This calls getUserSubs() which returns the following object:

expected result

When I call on subscriptions I get the expected array console (See "Works") snippet.

But when I try to iterate on the array subscriptions (See "Doesn't work"), then the contents of the function are not even called.

userSubs() is called for API data

async function getUserSubs(userId) {
  const ref = collection(db, "users", userId, "subscriptions")
  let subList = []
  try {
    const subIds = await getDocs(ref)
    subIds.forEach(subRef => {
      const docRef = subRef.data()
      getDocData(docRef.product).then(product => {
        subList.push(product)
      })
    })
    return subList

  } catch (e) {
    console.error("Error in getting subscriptions: ", e)
    return []
  }
}

Works

function getSubscriptions(userId) {
  getUserSubs(userId).then(subscriptions => {
    console.log(subscriptions)  // Works as intended 
  }
}

Doesn't work

function getSubscriptions(userId) {
  getUserSubs(userId).then(subscriptions => {
    subscriptions.forEach(x => {
      console.log(x)  // ISSUE: This isn't called 
    })
}

Also doesn't work

let a = []
getUserSubs(userId).then(subscriptions => {
  subscriptions.forEach(x => a.push(x))
})
console.log(a)

I know there are similar questions asked but after reading them I'm still not able to resolve my issue.

Similar issues:

engineer-x
  • 2,173
  • 2
  • 12
  • 25
  • 3
    Please [edit] your question to include data as text, rather than a picture of text. It would probably be easiest if you use `console.log(JSON.stringify(variable, null, 2))` and pasted the result here. Then we wouldn't need to see all of the `[[Prototype]]` nonsense :). – Heretic Monkey Dec 15 '21 at 03:17
  • The contents of the array returned are objects, not arrays. An object does not have a `.forEach()` method. Have you checked the console for errors? – Pointy Dec 15 '21 at 03:20
  • I've tried to mock AJAX call, you can clearly see, It is working as expected [link](https://codepen.io/kumarmasterpraveen/pen/KKXaLEG?editors=0011) – DecPK Dec 15 '21 at 03:21
  • Do read [weird array behaviour in javascript](https://stackoverflow.com/q/49838597/215552) for more information about why using `console.log` works and what that `i` means. – Heretic Monkey Dec 15 '21 at 03:21
  • 1
    See http://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron for why your third attempt doesn't work. – Heretic Monkey Dec 15 '21 at 03:24
  • @HereticMonkey Thanks for the tip! But it seems this function isn't called when I place in the `then()` function: `getUserSubs(userId).then(subscriptions => { console.log(JSON.stringify(subscriptions, null, 4)) })` – engineer-x Dec 15 '21 at 03:27
  • Once again: if you iterate through that array, each array element will be an object. You cannot use `.forEach()` to iterate through an object. – Pointy Dec 15 '21 at 03:31
  • @Pointy `JSON.stringify` works on objects and arrays; I was hoping that it would definitively say what type of thing `subscriptions` is. I think there's something else going on here that we can't see. Perhaps in `getUserSubs`. – Heretic Monkey Dec 15 '21 at 03:34
  • @HereticMonkey well I was going by the image posted in the question. – Pointy Dec 15 '21 at 03:36
  • I didn't think it be important but I now added the userSubs() function. I appreciate all of you! :) – engineer-x Dec 15 '21 at 03:39
  • I used `typeof(subscriptions)` and it returned `object`. So @Pointy has... a point. But the successful output has brackets and an index number, so wouldn't that be an array? – engineer-x Dec 15 '21 at 03:43
  • The outer object returned is an array, but you're trying to put a `.forEach` inside the outer `.forEach`. – Pointy Dec 15 '21 at 03:44

2 Answers2

1
getUserSubs(userId).then(subscriptions => {
    console.log(subscriptions)  // Works as intended 
}

No it doesn't. It only appears so because you are inspecting the live array that was mutated after it has been logged to the console.

Also doesn't work:

let a = []
getUserSubs(userId).then(subscriptions => {
  subscriptions.forEach(x => a.push(x))
})
console.log(a)

Yes, for rather obvious reasons: the array is logged before you fill it. It would need to be either

getUserSubs(userId).then(subscriptions => {
  let a = []
  subscriptions.forEach(x => a.push(x))
  console.log(a)
})

or

let a = []
const subscriptions = await getUserSubs(userId)
subscriptions.forEach(x => a.push(x))
console.log(a)

But none of these will solve your core problem: getUserSubs returns an empty array before it gets filled, in the lines

subIds.forEach(subRef => {
  const docRef = subRef.data()
  getDocData(docRef.product).then(product => {
    subList.push(product)
  })
})
return subList

you never wait for the getDocData promise. If you change that to

let subList = []
for (const subRef of subIds) {
  const docRef = subRef.data()
  const product = await getDocData(docRef.product)
  subList.push(product)
}
return subList

or just

return Promise.all(subIds.map(subRef => {
  const docRef = subRef.data()
  return getDocData(docRef.product)
}))

it would work, as described in the question you already found.

(This might still not work. subIds looks suspiciously like a firebase snapshot, which is not an array and can neither be iterated nor does it have a .map method. In that case, you'll need to use forEach+push manually).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • But shouldn't the `getUserSubs()` be fine since I do get a result (top screenshot) but I just can't seem to iterate through it? Which is your last point because yes I'm using Firebase v9 so I'll have to adapt the answer. – engineer-x Dec 15 '21 at 04:39
  • @engineer-x No, you're not getting a result, the array is empty when you get it. Iterating it immediately will iterate 0 entries. – Bergi Dec 15 '21 at 08:55
0

Honestly I wouldn't use the forEach() method. I think all you need to do to fix this is iterate over the results in a normal for loop.

for(let subscription of subscriptions) {
   console.log(subscription);
}

OR

for(let index in subscriptions) {
   console.log(subscriptions[index]);
}

If this doesn't do the trick, I'll open up a sandbox and look more in depth.

Edward
  • 335
  • 1
  • 3
  • 9
  • 3
    Mind If you could explain *I wouldn't use the forEach() method* What's wrong with `forEach` if `subscriptions` is an array... – DecPK Dec 15 '21 at 03:12
  • 3
    BTW, You shouldn't use `for..in` loop with arrays. – DecPK Dec 15 '21 at 03:15
  • @decpk I added the expected result in the original question. – engineer-x Dec 15 '21 at 03:17
  • @edward I tried both and still had the same result. I also tried the `for (let i = 0; i < subscriptions.length; subscriptions++)`. – engineer-x Dec 15 '21 at 03:20
  • Ok please edit your post with the array that's going wrong and I'll put them into a sandbox. As to why I wouldn't use the forEach method, I guess it really comes down to personal preference but I think the for loops using of and in are cleaner but anecdotally, I've run into instances where forEach failed when for loops didn't. – Edward Dec 15 '21 at 03:37