0

I cannot figure out what is wrong with how I'm implementing promises inside loops in a Firebase function. I get this error when running this function as a Firebase Scheduled function: "TypeError: undefined is not a function" specifying line 202 in my index.js (below). The function is not running to completion (all of the .thens are not completing). I have two other concerns:

  1. That I'm not using Promise.all correctly and not returning promises in the proper places.

  2. I get this error in my Firebase logs: "Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string." when trying to get the document within the path in section 2 of the code below. The path is correct and is indeed strings as I have verified by inspecting the reference object.

      checkForTweetsToDelete: function(db) {
    
        ///////
        // 1. Get first collction - streamers
        let streamersRef = db.collection('streamers');
        let streamersQuery = streamersRef.get()
          .then(streamersSnap => {
    
            var promises = [];
    
            ///////
            // 2. Get settings for each streamer - Here is where I get a "Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string."
            streamersSnap.forEach(streamerDoc => {
    
              let tweetSettingsRef = db.collection('streamers').doc((streamerDoc.id.toString())).collection('TweetSettings').doc('Settings');
              let settingsDoc = tweetSettingsRef.get()
                .then(doc => {
    
                    ///////
                    // 3. Get all 'no' Tweets for this streamer
                    let tweetsRef = db.collection('streamers').doc(doc.data().twitchId).collection('TwitterPosts');
                    let tweetsQuery = tweetsRef.where('deleted', '==', 'no').get()
                      .then(tweetsSnap => {
    
                        var tweetPromises = [];
    
                        tweetsSnap.forEach(tweetDoc => {
    
                          /////////////
                          // 4. Delete Tweet
                          const client = new Twitter({
                            subdomain: "api", // "api" is the default (change for other subdomains)
                            version: "1.1", // version "1.1" is the default (change for other subdomains)
                            consumer_key: APP_KEY, // from Twitter.
                            consumer_secret: APP_SECRET, // from Twitter.
                            access_token_key: USER_TOKEN, // from your User (oauth_token)
                            access_token_secret: SECRET // from your User (oauth_token_secret)
                          });
    
                          const user = client.post('statuses/destroy', {
                            id: tweetDoc.id,
                            trim_user: 1
                          })
                          .then(twitterResponse => {
    
                            ///////
                            // 5. Write to DB that this has been deleted.
                            let tweetRef = tweetsRef.doc(tweetDoc.id.toString());
                            let updatedDoc = tweetRef.set({
                              deleted : 'yes'
                            }, {merge: true}).then(() => {
                              return;
                            });
    
                            return Promise.all([updatedDoc]);
                            // 5
                            ///////
    
                          });
                          // 4
                          /////////////
    
                          tweetPromises.push(user);
    
                        });
    
                        return Promise.all(tweetPromises);
                      });
    
                      return Promise.all([tweetsQuery]);
                      // 3
                      ///////
    
                });
    
                promises.push(settingsDoc);
            });
    
            return Promise.all(promises);
            // 2
            ///////
          });
    
        return Promise.all([streamersQuery]);
        // 1
        ///////
      }
    

And my index.js

exports.scheduledFunction = functions.pubsub.schedule('every 1 minutes').onRun((context) => {
            console.log('This will run every 1 minutes!');

            var scheduled = require('./scheduledFunctions');
            if (admin.apps.length === 0 ) { admin.initializeApp(functions.config().firebase); }
            var db = admin.firestore();

            //scheduled.checkForTweetsToDelete(db);
            //return scheduled.checkForTweetsToDelete(db);
            return Promise.all( scheduled.checkForTweetsToDelete(db) ).then(function() { // Line 202
                console.log('Finished all promises!');
            });
        });

I removed most of the logic inside the .thens as well as the catchs for readability.

EDIT:

This question was helpful in understanding how this all should work: Firebase (Angular) Promises with Loops in Loops and after studying it I think I'm doing it just as explained, but still unsuccessful.

Nick H
  • 217
  • 4
  • 19
  • Have you considered using async/await to flatten your code structure? Might also solve your problem, but the example is a bit difficult to understand rn – kevmo314 Apr 17 '20 at 16:49
  • After struggling with this for two days I'm considering it, yes. Basically I've got a .then, a for loop, a .then, a for loop. – Nick H Apr 17 '20 at 17:11
  • Basically I've got a .then -> for loop -> .then -> for loop -> .then. The deepest for loop needs to finish its .then s before iterating to the next value in the parent for loop. – Nick H Apr 17 '20 at 17:17
  • Can't see why you might be getting that particular error, however once it is solved, you will need to address ... return Promise.all( scheduled.checkForTweetsToDelete(db) ).then(...) ... in which `scheduled.checkForTweetsToDelete(db)` returns Promise, not Array, therefore `Promise.all()` is inappropriate. Instead, simplify to ... return scheduled.checkForTweetsToDelete(db).then(...); – Roamer-1888 Apr 17 '20 at 19:03
  • 1
    @Roamer-1888 I did that and it fixed that error. I also solved the other problem. Was due to incorrect path on my part. – Nick H Apr 28 '20 at 20:41
  • Excellent. Well worked out. :-) – Roamer-1888 Apr 28 '20 at 20:50

1 Answers1

0

Turns out I was calling the incorrect path elsewhere in my code. I was getting a value from the database then using it to call another reference path but that value was undefined. After fixing that I was able to fix the other promise error by following Roamer-1888's advice.

Nick H
  • 217
  • 4
  • 19