1

I am using the code below to remove all docs in a collection in Firestore. My question is, how to execute a function which will return the result (promise) of 'deleteCollection' among with an array with the deleted docs? I want to loop over all the docs. I surely can query all the data first, loop through it and than execute this function, but than the data gets read twice.

Another option would be creating a empty global variable in which the delete function adds the doc. But how safe is this? If I am deleting two huge collections, the array gets populated with two different docs. That wouldn't be correct as well.

I also tried to modify the 'new Promise' return, but that function parameters can only contain resolve or reject, no arrays.

So I want a function which calls the deleteCollection and than I want to loop over the deleted data. Is this possible when I want to read the data only once?

function deleteCollection(db, collectionRef, batchSize) {
  var query = collectionRef.limit(batchSize);

  return new Promise(function(resolve, reject) {
      deleteQueryBatch(db, query, batchSize, resolve, reject);
  });
}

function deleteQueryBatch(db, query, batchSize, resolve, reject) {
  query.get()
      .then((snapshot) => {
          if (snapshot.size == 0) {
              return 0;
          }
          var batch = db.batch();
          snapshot.docs.forEach(function(doc) {
              batch.delete(doc.ref);
          });
          return batch.commit().then(function() {
              return snapshot.size;
          });
      }).then(function(numDeleted) {
          if (numDeleted <= batchSize) {
              resolve();
              return;
          }
          process.nextTick(function() {
              deleteQueryBatch(db, query, batchSize, resolve, reject);
          });
      })
      .catch(reject);
}

Edited from Bergi's answer

const results = deleteCollection(db, db.collection("deleteme"), 1)
//Now I want to work with the results array, 
//but the function is still deleting documents

function deleteCollection(db, collectionRef, batchSize) {
  return deleteQueryBatch(db, collectionRef.limit(batchSize), batchSize, new Array());
}
async function deleteQueryBatch(db, query, batchSize, results) {
  const snapshot = await query.get();
  if (snapshot.size > 0) {
    let batch = db.batch();
    snapshot.docs.forEach(doc => {
      results.push(doc); <-- the TypeError
      batch.delete(doc.ref);
    });
    await batch.commit();
  }
  if (snapshot.size >= batchSize) {
    return deleteQueryBatch(db, query, batchSize);
  } else {
    return results;
  }
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Oct 17 '17 at 21:04
  • @Bergi I literally copied the code from Google Docs: https://cloud.google.com/firestore/docs/manage-data/delete-data – J. Doe Oct 17 '17 at 21:05
  • Ouch. They definitely should avoid it as well. Where can one file bugs with the documentation? – Bergi Oct 17 '17 at 21:07
  • @Bergi I contacted them a few times with this link: https://firebase.google.com/support/contact/bugs-features/ they usually respond pretty quick (~1 day). – J. Doe Oct 17 '17 at 21:11
  • I don't have a Firebase account though, and that contact page requires one to sign in. – Bergi Oct 17 '17 at 21:14
  • @Bergi There are alot of Firebasers around here. If none will respond within a day on here, I will file a report with this case with a link to this question. Thanks. – J. Doe Oct 17 '17 at 21:18

1 Answers1

1

First of all, avoid the Promise constructor antipattern:

function deleteCollection(db, collectionRef, batchSize) {
  var query = collectionRef.limit(batchSize);
  return deleteQueryBatch(db, query, batchSize);
}

function deleteQueryBatch(db, query, batchSize) {
  return query.get().then(snapshot => {
    if (snapshot.size == 0) return 0;
    var batch = db.batch();
    snapshot.docs.forEach(doc => { batch.delete(doc.ref); });
    return batch.commit().then(() => snapshot.size);
  }).then(function(numDeleted) {
    if (numDeleted >= batchSize) {
      // I don't think process.nextTick is really necessary
      return deleteQueryBatch(db, query, batchSize);
    }
  });
}

(You might also want to use async/await syntax which really simplifies the code and makes the algorithm better understandable:

async function deleteQueryBatch(db, query, batchSize) {
  const snapshot = await query.get();
  if (snapshot.size > 0) {
    let batch = db.batch();
    snapshot.docs.forEach(doc => { batch.delete(doc.ref); });
    await batch.commit();
  }
  if (snapshot.size >= batchSize) {
    // await new Promise(resolve => process.nextTick(resolve));
    return deleteQueryBatch(db, query, batchSize);
  }
}

creating a empty global variable in which the delete function adds the doc. But how safe is this? If I am deleting two huge collections, the array gets populated with two different docs. That wouldn't be correct as well.

No, don't do this. Just pass the array that you're populating with the results as an argument through the recursive calls and return it in the end:

function deleteCollection(db, collectionRef, batchSize) {
  return deleteQueryBatch(db, collectionRef.limit(batchSize), batchSize, []);
}
async function deleteQueryBatch(db, query, batchSize, results) {
  const snapshot = await query.get();
  if (snapshot.size > 0) {
    let batch = db.batch();
    snapshot.docs.forEach(doc => {
      results.push(doc);
      batch.delete(doc.ref);
    });
    await batch.commit();
  }
  if (snapshot.size >= batchSize) {
    return deleteQueryBatch(db, query, batchSize, results);
  } else {
    return results;
  }
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks for your answer. However I get this error 'Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'push' of undefined'. I tried to log it and changed [] to new Array(), but that did not help. I tried results.push("test"), and I still get that error. Also, the deleteCollection returns a void so I am not sure how I can get the results if the TypeError is gone. I tried to add 'return' before 'deleteQueryBatch' but that does not wait for the return of deleteQueryBatch. – J. Doe Oct 17 '17 at 22:25
  • @J.Doe Ah, I forgot a bunch of `return` keywords and passing the `results` into the recursive call. – Bergi Oct 17 '17 at 22:30
  • 1
    Thanks for your time for creating this wonderful answer. I contacted the support line. I hope they will leave a comment on your answer which I added in the e-mail. Thanks! – J. Doe Oct 17 '17 at 22:47