Search code examples
async-awaitgoogle-cloud-functionsgoogle-cloud-storagegoogle-api-nodejs-clientasynccallback

Firebase Function Returns Before All Callback functions complete execution


I'm using the Google Storage NodeJS client library to list GCS Bucket paths.

Here's the code to the Firebase Function:

import * as functions from 'firebase-functions';
import { Storage } from '@google-cloud/storage';
import { globVars } from '../admin/admin';

const projectId = process.env.GCLOUD_PROJECT;
// shared global variables setup
const { keyFilename } = globVars;
// Storage set up
const storage = new Storage({
  projectId,
  keyFilename,
});

export const gcsListPath = functions
  .region('europe-west2')
  .runWith({ timeoutSeconds: 540, memory: '256MB' })
  .https.onCall(async (data, context) => {
    if (context.auth?.token.email_verified) {
      const { bucketName, prefix, pathList = false, fileList = false } = data;
      let list;
      const options = {
        autoPaginate: false,
        delimiter: '',
        prefix,
      };

      if (pathList) {
        options.delimiter = '/';

        let test: any[] = [];
        const callback =  (_err: any, _files: any, nextQuery: any, apiResponse: any) => {
          test = test.concat(apiResponse.prefixes);
          console.log('test : ', test);
          console.log('nextQuery : ', nextQuery);
          if (nextQuery) {
            storage.bucket(bucketName).getFiles(nextQuery, callback);
          } else {
            // prefixes = The finished array of prefixes.
            list = test;
          }
        }
       storage.bucket(bucketName).getFiles(options, callback);
      }

      if (fileList) {
        const [files] = await storage
          .bucket(bucketName)
          .getFiles(options);
        list = files.map((file) => file.name);
        
      }

      return { list }; //returning null as it exec before callback fns finish

    } else {
      return {
        error: { message: 'Bad Request', status: 'INVALID_ARGUMENT' },
      };
    }
  });

My problem is that my Firebase function returns the list (null) before all the callback functions finish execution.

Could someone spot and point out what needs to be changed/added to make the function wait for all the callback functions to finish. I've tried adding async/await but can't seem to get it right.


Solution

  • The reason for your error is that you use a callback. It's not awaited in the code. I would recommend to turn the callback code to a promise. Something like this.

    import * as functions from "firebase-functions";
    import { Storage } from "@google-cloud/storage";
    import { globVars } from "../admin/admin";
    
    const projectId = process.env.GCLOUD_PROJECT;
    // shared global variables setup
    const { keyFilename } = globVars;
    // Storage set up
    const storage = new Storage({
      projectId,
      keyFilename,
    });
    
    const getList = (bucketName, options) => {
      return new Promise((resolve, reject) => {
        let list;
        let test: any[] = [];
        const callback = (
          _err: any,
          _files: any,
          nextQuery: any,
          apiResponse: any
        ) => {
          test = test.concat(apiResponse.prefixes);
          console.log("test : ", test);
          console.log("nextQuery : ", nextQuery);
          if (nextQuery) {
            storage.bucket(bucketName).getFiles(nextQuery, callback);
          } else {
            // prefixes = The finished array of prefixes.
            list = test;
          }
    
          resolve(list);
        };
    
        try {
          storage.bucket(bucketName).getFiles(options, callback);
        } catch (error) {
          reject(eror);
        }
      });
    };
    
    export const gcsListPath = functions
      .region("europe-west2")
      .runWith({ timeoutSeconds: 540, memory: "256MB" })
      .https.onCall(async (data, context) => {
        if (context.auth?.token.email_verified) {
          const { bucketName, prefix, pathList = false, fileList = false } = data;
          let list;
          const options = {
            autoPaginate: false,
            delimiter: "",
            prefix,
          };
    
          if (pathList) {
            options.delimiter = "/";
    
            list = await getList(bucketName, options);
          }
    
          if (fileList) {
            const [files] = await storage.bucket(bucketName).getFiles(options);
            list = files.map((file) => file.name);
          }
    
          return { list }; //returning null as it exec before callback fns finish
        } else {
          return {
            error: { message: "Bad Request", status: "INVALID_ARGUMENT" },
          };
        }
      });
    
    

    I'm not sure if the part with fileList will work as expectedt. It looks like the API doesn't support await but only callbacks.