Search code examples
javascriptnode.jsfirebasefirebase-realtime-databasegeofire

Maximum call stack error for geofire ( firebase-real time database )


I am using real time database as firebase and i used geofire javascript package to get near by data from firebase. but some time it automatically gives error like :

RangeError: Maximum call stack size exceeded
at ChildrenNode.isLeafNode (api/node_modules/firebase-admin/node_modules/@firebase/database-compat/dist/index.standalone.js:8956:50)
at ChildrenNode.equals (api/node_modules/geofire/dist/geofire/index.cjs.js:8461:24)

this is my sample query to find near by things from firebase realtime database using geofire

while (some amount) { 
 geoDriverId = await(new Promise((resolve, reject) => {
try {
  geoDriverId = []
  geoDriverIds = []
  const geoQuery = config.firebase.table.query({
    center: [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)],
    radius: i
  })
  let listener = geoQuery.on("key_entered", (key, location, distance) => {
    geoDriverIds.push({ key, distance });
  });
  geoQuery.on("ready", (key, location, distance) => {
    listener.cancel();
    geoQuery.cancel();
    resolve(geoDriverIds);
    return;
  });
} catch (e) {
  console.log("e", e)
}
}))
}

Solution

  • If the intention is to poll for the initial results of the query, consider using the following method instead (this is effectively the code you provided in the question, cleaned up and turned into a method):

    // Executes a GeoFire query around the given center and returns only the initial results as a Promise.
    function pollGeofireLocation(center: Geopoint, radius: number) {
      return new Promise(resolve => {
        const queryResults = [],
              geoQuery = config.firebase.table.query({ center, radius });
    
        geoQuery.on("key_entered", (key, location, distance) => {
          queryResults.push({ key, distance }); // consider storing location
        });
    
        geoQuery.on("ready", () => {
          geoQuery.cancel(); // unsubscribe all event listeners and destroy query
          // consider sorting queryResults before resolving the promise (see below)
          resolve(queryResults);
        });
      });
    }
    

    Usage:

    const pick_up_geopoint = [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)];
    const results = await pollGeofireLocation(pick_up_geopoint, 10);
    // results is an array of { key: string, distance: number } objects,
    // in the order they were discovered by the query - not necessarily by distance
    

    To sort the results by the distance from the center point:

    const pick_up_geopoint = [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)];
    const nearbyDriverIDs = await pollGeofireLocation(pick_up_geopoint, 10);
    // nearbyDriverIDs is an array of { key: string, distance: number } objects,
    // in the order they were discovered by the query - not necessarily by distance
    
    nearbyDriverIDs.sort(({ distance: a_dist }, { distance: b_dist }) => a_dist - b_dist);
    // nearbyDriverIDs is now an array of { key: string, distance: number } objects,
    // ordered by their distance from the center point, closest to furthest.
    // nearbyDriverIDs[0], if present (nearbyDriverIDs may be empty!), is the closest driver
    

    If the intention is to find only the closest result, you can use the following method to progressively search for results:

    // Executes a GeoFire query around the given center, performing an expanding search where the first result is returned in a Promise.
    function findClosestResultToGeofireLocation(center: Geopoint, maxRadius: number, stepRadius: number = 5, startRadius: number = 0) {
      return new Promise((resolve, reject) => {
        const queryResults = [],
              geoQuery = config.firebase.table.query({
                center,
                radius: Math.min(maxRadius, startRadius || stepRadius) // if startRadius is provided, use its value, otherwise start at stepRadius
              });
    
        geoQuery.on("key_entered", (key, location, distance) => {
          queryResults.push({ key, distance }); // consider storing location
        });
    
        geoQuery.on("ready", () => {
          // found at least one result? return the closest one
          if (queryResults.length > 0) {
            geoQuery.cancel(); // unsubscribe all event listeners and destroy query
            const closest = queryResults
              .reduce((closest, result) => result.distance < closest.distance ? result : closest);
            resolve(closest);
            return;
          }
    
          // no results for search criteria and at max search radius? reject with an error
          if (geoQuery.radius() >= maxRadius) {
            geoQuery.cancel(); // unsubscribe all event listeners and destroy query
            reject(new Error(`No results found within ${maxRadius}km`));
            // could also use resolve(null);
            return;
          }
    
          // expand search area by increasing the radius by stepRadius kilometers,
          // stopping at maxRadius kilometers
          // note: you may want to add a delay before calling this
          geoQuery.updateCriteria({
            radius: Math.min(maxRadius, geoQuery.radius() + stepRadius)
          });
        });
      });
    }
    

    Usage:

    const pick_up_geopoint = [parseFloat(pick_up_latitude), parseFloat(pick_up_longitude)];
    const closestDriverID = await findClosestResultToGeofireLocation(pick_up_geopoint, 25);
    // closestDriverID will be the closest driver to pick_up_geopoint.
    // With the above arguments, the search will try for drivers <5km away, then
    // <10km away, then <15km away, then <20km away and then finally <25km away.
    // If a driver is not in range, the returned promise will reject with an error.