With firebase you can write cloud functions in multiple files.
I have two functions, named "function1" and "function2", located in two separate files.
File: function1.js
const functions = require('firebase-functions');//This will be executed regardless of the function called
exports.function1 = functions.https.onRequest((request, response) => {
// ...
});
File: function2.js
const functions = require('firebase-functions');//This will be executed regardless of the function called
const admin = require('firebase-admin');//This will be executed regardless of the function called
exports.function2 = functions.https.onRequest((request, response) => {
// ...
});
Now I use index.js to export these files as shown here.
File: index.js
const function1 = require('./function1');
const function2 = require('./function2');
exports.function1 = function1.function1;
exports.function2 = function2.function2;
When I execute function1 I can access the "admin" variable from function2.
The obvious fix for this is to not declare the variable on the global scope.
Modified File: function2.js
const functions = require('firebase-functions');//This will be executed regardless of the function called
exports.function2 = functions.https.onRequest((request, response) => {
const admin = require('firebase-admin');//This will only be executed when function2 is called
// ...
});
Now the "admin" variable is only initialized when I call function2 and not function1.
Cloud Functions often recycles the execution environment of a previous invocation.
If you declare a variable in global scope, its value can be reused in subsequent invocations without having to be recomputed.
But now the "admin" variable will not be reused in subsequent invocations because it was not declared in the global scope.
So my question is how can store the "admin" variable on the global scope(so that it could be reused for multiple instances), but not have it initialized when function1 is called?
What you're trying to do is not possible. By definition, two server instances cannot share memory. They are completely isolated from each other. Each function invocation runs isolated in its own instance, and two different functions can never reuse the same instance. You will have to accept that the global memory space for function1 will never be seen by function2.
Watch this video to learn more: https://www.youtube.com/watch?v=rCpKxpIMg6o
After some research the answer is to do lazy initialization of global variables.
If you initialize variables in global scope, the initialization code will always be executed via a cold start invocation, increasing your function's latency.
If some objects are not used in all code paths, consider initializing them lazily on demand:
File: function1.js
exports.function1 = functions.https.onRequest((request, response) => {
// ...
});
File: function2.js
exports.function2 = functions.https.onRequest((request, response) => {
admin = admin || require('firebase-admin');
// ...
});
Now I use index.js to export these files as shown here.
File: index.js
const functions = require('firebase-functions');
const admin;
const function1 = require('./function1');
const function2 = require('./function2');
exports.function1 = function1.function1;
exports.function2 = function2.function2;
This is particularly important if you define several functions in a single file or multiple files, and different functions use different variables.
Unless you use lazy initialization, you may waste resources on variables that are initialized but never used.
Related
I am developing a project which will have +100 cloud functions, so for making the work easier I am structuring all my functions like this:
/functions
/src
spotify/
searchMusic.function.js
...
auth/
signUp.function.js
...
...
index.js // Auto export every cloud function
In each file that ends with .function.js I export an unique cloud function. My question is that, as I don't have more than one google cloud function per file, should I make lazy imports/initializations?
For example, I know that it is useful to do this when there are two functions in a module and one of them doesn't use a package which is used in the other:
const is_f1_admin_initialized = false;
exports.f1 = functions.region...{
const admin = require("firebase-admin");
// Lazy initialization
if(!is_f1_admin_initialized) {
// Initialize the admin SDK
...
is_f1_admin_initialized = true;
}
}
exports.f2 = functions.region...{}
But in my situation, where I only have f1 in f1.function.js, will lazy imports/initializations reduce the cold start? Or will it be better to make all imports in the global scope?
Thank you.
UPDATE
That's what I mean:
"use-strict";
const functions = require("firebase-functions");
const admin = require("firebase-admin");
// All my imports...
// The admin SDK can only be initialized once.
try {
const googleCloudServiceAccount = require("../../utils/json/googleCloudServiceAccount.json");
admin.initializeApp({
...
});
} catch (e) {}
// The unique cloud function in the module
exports.function = functions.region...
// All my helper functions...
But in my situation, where I only have f1 in f1.function.js, will lazy imports/initializations reduce the cold start?
No, it will make no difference.
In either case (running in either the global scope, or within the function), the code is in the critical path of serving the first request. Changing the scope doesn't change the time it takes for that code to run.
The only time that moving code out of the global scope will help is when you have multiple functions that do not share that code. Moving the execution of that code into the function simply ensures that other functions don't unnecessarily execute it.
I am trying to reuse a variable between multiple jest.mock calls in a same test file.
jest.mock calls are hoisted to the top by babel-jest so usually the variables outside the mock function scope are not allowed to be accessed. But the error message says:
This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with mock are permitted.
So If I prefix the variables with mock, I no longer receive the error but the values are all still set to undefined inside the mock. I don't understand what the error message means by "lazily required". Is it possible at all to share variables between jest.mock calls?
Reproducable example:
const mockReusableValue = { x: 5 };
jest.mock('../someModule', () => {
console.log(mockReusableValue); // undefined
return {};
});
babel-jest will hoist the module mock on the top of the scope meaning you can define the mock-prefixed variable in the outer scope and reuse it in your module mock like this
test.js
const mockData = 3;
describe('someModule`s someMethod returning mockData', () => {
jest.mock('./someModule', () => ({
someMethod: jest.fn().mockReturnValue(mockData)
}))
})
working example
I am building an application composed of a set of modules that get loaded when the application bootstrap:
const MODULES = []
function registerModules(config) {
return modules.map(module => (new module(config)).install());
}
function bootstrap() {
MODULES = registerModules({...});
}
The above of-course will raise an error. What I want to do is to assign the MODULE constant only when the app start but then it should be fixed. Is this possible?
Initialisation of MODULES has to happen inside bootstrap because of the config variable that I have to pass to registerModules.
If indeed you want to:
Have an initialised, but empty MODULES array during the time that bootstrap() is not yet called, and
Make MODULES immutable once it has been populated, and
Keep the current function signature of registerModules unchanged;
Then you could do it as follows:
function bootstrap() {
Object.freeze(Object.assign(MODULES, registerModules({...})));
// OR:
// MODULES.push(...registerModules({...}));
// Object.freeze(MODULES);
}
If you don't insist on the existence of MODULES before bootstrap() is called, and you are open to store that information inside an object, then you could proceed as follows:
const globals = {};
function bootstrap() {
globals.MODULES = registerModules({...});
Object.freeze(globals.MODULES);
}
Once you are happy with globals, you can also freeze that object:
Object.freeze(globals);
For my user registration I have
const express = require ('express');
const userRouter = express.Router ();
userRouter.get ('/', function getUserList (req, res) {
let User = require ('../models').User;
User.find ({}, function (err, list) {
res.json (list);
});
});
userRouter.post ('/', function createUser (req, res) {
let User = require ('../models').User;
if (req.body.username && req.body.password)
User.create (req.body, function (err, user) {
res.json (user);
});
});
... 3 more functions with the same `let User` ...
module.exports = userRouter;
Here, I have to require the module models twice. I tried setting the User variable as a global variable up at the top of the program, like
const express = ..., userRouter = ...
var User = ...
However this User variable is still not accessible inside my callback functions.
Is requiring the User module multiple times the correct way to do this or am I missing something?
edit: Inside the callback functions, the User variable is undefined.
As #Doug mentioned, you should pass global to the user variable like this:
global.user = ...
This process essentially turns user into a globally accessible variable. You can read more about Node's global here to understand what it does as well as understand what it's implications are too: http://stackabuse.com/using-global-variables-in-node-js/
To #charloetfl ‘s point if it is just within the file, declaring it outside the callback or on top of the file should do. If we are talking about access across all modules and files within the project then adding it to the global object is the way to go about it in node as #doug and #andrewl mentioned.
So I'm writing a multiplayer game with Socket.io and most of the socket calls are handled in the main file (app.js) including storing usernames and the sockets they're connected to.
But I'd like to create a separate file (game.js) that handles all the game code including socket emissions to certain rooms. However to do that I'll need access to my array with the users/sockets stored in it (which is in app.js)
So I'm wondering what the best way to share the variable would be? Should I pass the the array reference through every function that I need it in?
Or should I write a function that is called once and creates a global variable (or the scope that I need it in) with a reference to the array?
Also If I should ever need to share the same dependency across multiple files should I call require in each one of them?
About Modules and the use Global/Shared State
An interesting aspect of modules is the way they are evaluated. The module is evaluated the first time it is required and then it is cached. This means that after it has been evaluated no matter how many times we require it again, we will always get the same exported object back.
This means that, although Node provides a global object, it is probably better to use modules to store shared stated instead of putting it directly into the global object. For instance, the following module exposes the configuration of a Mongo database.
//module config.js
dbConfig = {
url:'mongodb://foo',
user: 'anakin',
password: '*******'
}
module. exports = dbConfig;
We can easily share this module with as many other modules as we want, and every one of them will get the same instance of the configuration object since the module is evaluated only once, and the exported object is cached thereon.
//foo.js
var dbConfig1 = require('./config');
var dbConfig2 = require('./config');
var assert = require('assert');
assert(dbConfig1==dbConfi2);
So, for your specific problem, the shared state that you want to share can reside in a singleton object exposed by whatever module you have. Just make sure your singleton object is the one being exposed in your module and you will always get a reference back to it every time you require it.
If by 'variable' you mean reference to the socket - you may want to consider passing a callback or module to game.js which handles the emission - but that game.js calls when necessary.
Like Edwin Dalorzo mentioned, having a separate file for all your variables seems the best.
I've had a similar problem for a few hours now because I didn't know that variables were persistent. The scenario that I had was:
I have two files cli.ts and main-lib.ts. cli.ts reads user input, and depending on the input, runs functions in main-lib.ts. While main-lib.ts is busy validating the input, cli.ts uses some global variables that main-lib.ts generates when a test passes. The only limitation is that I can't mix the main-lib.ts code with the cli.ts, I can only share the function callValidateFunction.
The issue that I had originally thought of was: if I were to make a global-vars.ts file, the variables' data will still be different for each call of require (i.e., calling setVar(...) will only change the variable value that was imported.
However, thanks to Edwin's answer, I managed to implement a bridge:
// cli.ts
import { setVar, getVar } from "./var-bridge";
import { callValidateFunction } from "./main-lib";
function run(args: string[]): void {
// ...
if (arg == "--email") {
// Set the test status.
setVar("testStatus", "pending");
// Validate the input email address.
callValidateFunction("validateEmail", nextArg());
// Get the testStatus.
const testStatus: string = getVar("testStatus");
}
// ...
}
// main-lib.ts
import { setVar, getVar } from "./var-bridge";
const funcs: {name: string, func: (arg: string) => boolean} = {};
funcs.validateEmail = function(arg: string): boolean {
let passed: boolean = false;
// ...
return passed;
};
function callValidateFunction(functionName: string, arg: string): void {
// ...
const passed = funcs[functionName](arg);
if (passed) setVar("testStatus", "passed");
}
// ...
// var-bridge.ts
const variables: {name: string, value: any} = {
"testStatus": "",
// ...
};
function setVar(varName: string, varValue: any): void {
variables[varName] = varValue;
}
function getVar(varName: string): any {
return variables[varName];
}
export { setVar, getVar };