Firebase定时云函数触发报错Error: Process exited with code 16,排查房源超时未支付自动取消预订功能异常
Let’s break down exactly what’s causing your function to fail with exit code 16, and fix the underlying issues step by step.
Key Issues Identified
1. Unhandled Asynchronous Operations (Root Cause of Exit Code 16)
Exit code 16 in Firebase Cloud Functions almost always means your function is exiting before all asynchronous work completes. Your current code doesn’t properly return or await the Firestore update promises, so the runtime kills the process early when it thinks the function is done.
2. Mismatched Timestamp Types
In your reserveApt function, you’re using admin.database.ServerValue.TIMESTAMP — this is a Realtime Database-specific value. When stored in Firestore, it doesn’t translate to a valid Firestore Timestamp, which breaks your query in the scheduled function (since you’re comparing it to a Date object).
Fixed Code
First: Fix the Reservation Timestamp in reserveApt
Replace the Realtime Database timestamp with Firestore’s native server timestamp:
exports.reserveApt = functions.https.onCall((data, context) => { if (!context.auth) { return { result: "failed", msg: "you are not authenticated" }; } const doc = admin.firestore().collection("properties").doc(data.aptId); return doc.get().then((aptDoc) => { const apt = aptDoc.data(); if (apt.status !== "Remaining") { return { result: "failed", msg: "Apartment is already reserved or sold" }; } return doc .update({ agent: data.agent, reservedBy: data.reserveeName, reservedByPhoneNumber: data.reserveePhone, // Use Firestore's native server timestamp reservationDate: admin.firestore.FieldValue.serverTimestamp(), status: "Reserved", paymentIsDone: false, }) .then(() => { console.log("apt reserved"); return {result: "success"}; }); }); });
Second: Fix the Scheduled Function to Handle Async Properly
Use async/await for cleaner code, await all update operations, and use Firestore Timestamps for accurate comparisons:
exports.scheduledFunction = functions.pubsub .schedule("every 5 minutes") .onRun(async (context) => { console.log("This will be run every 5 minutes!"); const now = admin.firestore.Timestamp.now(); // Calculate 5 minutes ago using Firestore Timestamp (avoids date conversion issues) const compareTimestamp = admin.firestore.Timestamp.fromMillis(now.toMillis() - 5 * 60 * 1000); const querySnapshot = await admin .firestore() .collection("properties") .where("status", "==", "Reserved") .where("reservationDate", "<", compareTimestamp) .where("paymentIsDone", "==", false) .get(); // Collect all update promises to run them in parallel const updatePromises = []; querySnapshot.forEach((doc) => { console.log(`Canceling reservation for property: ${doc.id}`); const updatePromise = admin.firestore().collection("properties").doc(doc.id).update({ agent: "", reservedBy: "", reservedByPhoneNumber: "", reservationDate: null, status: "Remaining", }); updatePromises.push(updatePromise); }); // Wait for all updates to finish before exiting the function await Promise.all(updatePromises); console.log(`Successfully canceled ${updatePromises.length} reservations`); return null; });
What Changed & Why
- Proper Async Handling: We use
async/awaitandPromise.allto ensure the function waits for all Firestore updates to complete before exiting. This eliminates the exit code 16 error. - Firestore Timestamp Consistency: By using
admin.firestore.FieldValue.serverTimestamp()in reservations and comparing directly with Firestore Timestamps in the scheduled function, we avoid type mismatches that would break your query. - Clear Logging: Added per-document and summary logs to help you verify the function is working as expected.
内容的提问来源于stack exchange,提问作者Hawkar Shwany

