aseidma
aseidma

Reputation: 752

Returning a promise nested in forEach, in a .then function

I am struggling with returning a promise (or multiple promises) nested in a forEach function, which is nested in a .then function.

The second .then does not wait for the promise inside the first .then to complete and therefore does not return 'this.topics' (empty array instead) and logs undefined instead of data.

I think it is best to wrap your head around by looking at the code, so here it is:

getTopics() {

      this.$fireStore
        .collection("courses")
        .doc(this.$store.state.courses.currentlyPreviewing.cid)
        .collection("sections")
        .get()
        .then(snapshot => {

            snapshot.forEach(sec => {
              return this.$fireStore
                .collection("courses")
                .doc(this.$store.state.courses.currentlyPreviewing.cid)
                .collection("sections")
                .doc(sec.id)
                .collection("topics")
                .orderBy("order")
                .get()
                .then(snapshot => {
                  snapshot.forEach(doc => {
                    this.topics.push({
                      data: doc.data(),
                      topic_id: doc.id,
                      sectionOfTopic_id: sec.id
                    });
                  });
                  console.log('First done!') // Only logged after data and this.topics
                });
            })
        })
        .then((data) => {
          console.log(data) // Logs undefined
          console.log(JSON.stringify(this.topics)) // Logs empty array
          return this.topReady = true;
        });

Upvotes: 2

Views: 153

Answers (2)

AJ Hurst
AJ Hurst

Reputation: 368

You're working with a list or promises rather than a single promise, so take a look at using Promise.all instead (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).

The Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.

getTopics() {

  this.$fireStore
    .collection("courses")
    .doc(this.$store.state.courses.currentlyPreviewing.cid)
    .collection("sections")
    .get()
    .then(snapshot => {
        const promises = snapshot.map(sec => {
          return this.$fireStore
            .collection("courses")
            .doc(this.$store.state.courses.currentlyPreviewing.cid)
            .collection("sections")
            .doc(sec.id)
            .collection("topics")
            .orderBy("order")
            .get()
            .then(snapshot => {
              snapshot.forEach(doc => {
                this.topics.push({
                  data: doc.data(),
                  topic_id: doc.id,
                  sectionOfTopic_id: sec.id
                });
              });
              console.log('First done!') // Only logged after data and this.topics
            });
        })

        return Promise.all(promises)
    })
    .then((data) => {
      console.log(data) // Logs undefined
      console.log(JSON.stringify(this.topics)) // Logs empty array
      return this.topReady = true;
    });

Upvotes: 3

TKoL
TKoL

Reputation: 13892

You didn't really specify which bit is the promise -- this.topics.push ? this.$fireStore...get()?

I'm going to assume the latter one.

What you want to do is something more like this:

.then(snapshot => {
        var promises = snapshot.map(sec => {
          return this.$fireStore.
                       ...
                       .get()
          });
        return Promise.all(promises);
 });

When you do a .map and return a promise in the callback, you make an array of promises. Promise.all on an array of promises joins them into a single promise.

Upvotes: 2

Related Questions