在 answer by @Mises 的基础上,我可以提供一些额外的上下文。
作为帮助开发人员避免错误和竞争条件的内置保护的一部分,initializeApp() 将在为同一应用程序名称调用两次时抛出错误(在不提供名称的情况下使用 "[DEFAULT]" 代替)。它也是以这种方式设计的,因为它更容易抛出错误,而不是将传递到每个 initializeApp() 调用的配置对象与前一个调用进行比较。由于这种行为,initializeApp() 应该在您的应用程序中只被调用一次,要么在当前文件的顶部,要么在某些中心依赖项中(例如 app.js)。然后当你需要它时,你可以使用getApp()、getFirestore()等将它带入当前文件。
getApp() 和 getApps() 函数是 Firebase SDK 功能的一部分,您可以在一个应用程序中使用多个项目。这个功能的使用是documented here。
加载 Firebase 依赖项
对于某些开发人员来说,Firebase 是相当严重的依赖项(尤其是对于遗留的 JavaScript SDK)。所以他们不想不必要地加载它是可以理解的。这对于交互时间很重要的基于 Web 的应用程序或尝试优化 Cloud Functions for Firebase 的冷启动时间以获得最佳响应时间时尤为重要。
在 optimizing cold-start times 上 @doug-stevenson 的这个旧视频中,Doug 介绍了如何使用布尔标志来指示 Firebase Admin SDK 是否已初始化。这允许不使用 Admin SDK 的函数跳过加载它并更快地返回结果。
// note: legacy syntax being used for historical purposes
const functions = require("firebase-functions");
let is_f1_initialized = false;
// a HTTPS Request function that uses the Admin SDK
exports.f1 =
functions.https.onRequest((req, res) => {
const admin = require("firebase-admin");
if (!is_f1_initialized) {
admin.initializeApp();
is_f1_initialized = true;
}
// does stuff, using admin SDK
});
// a HTTPS Request function that doesn't use the Admin SDK
exports.f2 =
functions.https.onRequest((req, res) => {
// does stuff
});
一些开发人员不喜欢在他们的全局范围内乱放这样的标志,因此他们寻找一个即时的替代方案。这采取了检查遗留 JavaScript SDK 中 firebase.apps 和 Admin SDK 中 admin.apps 长度的形式。
// note: this code block uses the legacy "firebase-admin" library syntax
import * as admin from "firebase-admin";
console.log(admin.apps.length); // logs '0'
admin.initializeApp();
console.log(admin.apps.length); // logs '1'
同样的方法也适用于客户端 JavaScript SDK:
// note: this code block uses the legacy "firebase" library syntax
import * as firebase from "firebase";
console.log(firebase.apps.length); // logs '0'
firebase.initializeApp(config);
console.log(firebase.apps.length); // logs '1'
对于单项目应用程序,这很快成为检查默认应用程序是否已初始化的事实上的标准,导致以下行随处可见(尤其是在使用每个文件一个组件的框架时):
// note: historical legacy "firebase" library syntax used on purpose
const app = firebase.apps.length ? firebase.app() : firebase.initializeApp(config);
// or for those against implied type coercion to Booleans:
// const app = !firebase.apps.length ? firebase.initializeApp(config) : firebase.app();
const db = firebase.firestore(app);
或者
// note: historical legacy "firebase" library syntax used on purpose
if (!firebase.apps.length) {
firebase.initializeApp(config);
}
const db = firebase.firestore();
摘要/TL:DR;
随着 "firebase" 和 "firebase-admin" 转向模块化 Firebase JavaScript SDK,使用遗留代码的开发人员和新手正在按照 modular SDK migration guide 对其进行更新。
这导致以下遗留代码:
// note: historical legacy "firebase" library syntax used on purpose
const app = !firebase.apps.length ? firebase.initializeApp(config) : firebase.app();
被一对一地翻译成这个现代代码:
const app = !getApps().length ? initializeApp(config) : getApp();
此行的主要目的是获取正确初始化的 FirebaseApp 类实例而不引发错误,您可以将其传递给 SDK 中包含的 Firebase 服务的入口点函数,例如 Analytics 和 Cloud Firestore。
一窥幕后花絮
查看如何默认的应用程序实例在SDK中的服务之间进行手球,你可以看看源代码。 FirebaseApp相关函数的实现类似于下面的代码。
笔记:我省略了一些验证并重命名了一些变量以保持简洁,您应该查看full source 或查看API reference 了解详细信息。
const _apps = new Map<string, FirebaseApp>();
const DEFAULT_ENTRY_NAME = "[DEFAULT]";
// initializes the given app, throwing an error when already initialized
export function initializeApp(options: FirebaseOptions, name?: string | undefined): FirebaseApp {
name = name || DEFAULT_ENTRY_NAME;
if (_apps.has(name)) throw new Error("already initialized");
const app = new FirebaseApp(options, name)
_apps.set(name, app);
return app;
}
// returns a read-only array of initialized apps, doesn't throw errors
export function getApps(): FirebaseApp[] {
return Array.from(_apps.values())
}
// gets the named/default app, throwing an error if not initialized
export function getApp(name: string = DEFAULT_ENTRY_NAME): FirebaseApp {
const app = _apps.get(name);
if (!app && name === DEFAULT_ENTRY_NAME) return initializeApp();
if (!app) throw new Error(name + " not initialized");
return app;
}
// marks the given app unusable and frees its resources
export async function deleteApp(app: FirebaseApp): Promise<void> {
const name = app.name;
if (!_apps.has(name)) return; // already deleted/started deletion?
_apps.delete(name);
await Promise.all(
Object.values(app._providers)
.map(provider => provider.release())
)
app.isDeleted = true;
}
SDK 中可用的每个服务都有一个入口点函数。在旧的命名空间 SDK 中,它采用 firebase.firestore() 的形式,而现代模块化 SDK 使用 getFirestore() 代替。这些入口点函数中的每一个都遵循类似的策略,并且看起来类似于下面的代码。
笔记:和以前一样,这是一个简化版本。有关详细信息,请参阅完整的source 和API reference。
export function getFirestore(app?: FirebaseApp) {
app = app || getApp(); // use given app or use default
return app._providers.get('firestore') || initializeFirestore(app, DEFAULT_SETTINGS)
}