Firebase的“getApps().length”检查来自哪里?

ars1skjm  于 2023-01-27  发布在  其他
关注(0)|答案(2)|浏览(108)

我对Firebase不是很有经验,最近我一直在用Next.js + Firebase做一些事情,偶然发现了这行必要的代码:

const app = !getApps().length ? initializeApp(config) : getApp()

据我所知,这可以防止创建多个配置相同的Firebase应用程序。但是,首先,这是从哪里来的?其次,getApps是如何函数知道所有其他非DEFAULT的应用程序吗?它的返回值是可变的还是只读的?它如何getApp()函数(最后没有“s”)甚至知道哪个应用程序是我默认返回的,我不向它传递任何东西...
我找不到任何关于这一点,也没有在Firebase的文档,也没有从他们的主要发言人,如大卫东,托德Kerpelman,弗兰克货车普费伦。我知道,Firebase的文档是字面上最糟糕的星球上,马里奥游戏“要好得多,但即使这样...
帮助:)

1mrurvl1

1mrurvl11#

在Firebase SDK中有这样的内容:

const FirebaseApp: FirebaseApp[]

export function initializeApp(options: FirebaseOptions, name?: string | undefined) {
    return !name ? FirebaseApp = [...FirebaseApp, new FirebaseApp(options, 'default')] : FirebaseApp = [...FirebaseApp, new FirebaseApp(options, 'default')]
  }

export function getApps() {
  return FirebaseApp
}

export function getApp(name?: string) {
  return !name ? FirebaseApp.filter(n => n.name === 'default') : FirebaseApp.filter(n => n.name === name)
}

Firebase JS SDK采用TypeScript编写。
在你的代码中,你不需要const app = ...,只是滥用所有的Firebase函数。函数getFirestore()会给你一个你需要使用的Firebase示例,与getApp()相同。您可以使用enableIndexedDbPersistence(getFirebase()),这样您就可以在客户端浏览器中本地缓存数据,而不需要任何Redux/Pinia/Vuex解决方案。如果您使用onSnapshot()侦听器,这将减少数据库查询。或者将getDocFromCache()getDoc()结合使用。

    • 编辑:**

好吧,如果你忘记了OOP是如何工作的,并且开始用函数式/结构化编程来思考,那么它是如何工作的就开始变得显而易见了。你的应用程序是封装的,但是你有一个"getter""setter"来处理它。比OOP更好,更容易理解。没有任何设计模式需要学习。而且库可以很容易地被编译器/捆绑器树摇动,所以它是轻量级的。

jqjz2hbq

jqjz2hbq2#

在@Mises的answer的基础上,我可以提供一些附加的上下文。
作为帮助开发人员避免错误和竞争条件的内置保护的一部分,如果为同一应用程序名调用两次,initializeApp()将抛出错误(这里没有给出名称,而是使用"[DEFAULT]")。这样设计的另一个原因是,抛出一个错误更容易,而不是将传入每个initializeApp()调用的配置对象与前一个进行比较。由于这种行为,initializeApp()应该在应用程序中只被调用一次,或者在当前文件的顶部,或者在某个中央依赖项中(例如app.js),然后当需要它时,可以使用getApp()getFirestore()等将其引入当前文件。
getApp()getApps()函数是Firebase SDK功能的一部分,您可以在一个应用程序中使用多个项目。此处记录了此功能的使用。

加载Firebase依赖关系

对于一些开发者来说,Firebase是相当严重的依赖(特别是对于遗留的JavaScript SDK)。所以他们不想不必要地加载它是可以理解的。这对于基于Web的应用程序来说尤其重要,因为交互时间很重要,或者当试图优化Firebase的云函数的冷启动时间以获得最佳响应时间时。
在这个由@doug-stevenson制作的关于optimizing cold-start times的旧视频中,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(如Analytics和Cloud Firestore)中包含的Firebase服务的入口点函数。

引擎盖下的一瞥

要了解如何在SDK中的服务之间处理默认应用程序示例,您可以查看源代码。与FirebaseApp相关的函数的实现类似于以下代码。

**注意:**为了简洁起见,我省略了一些验证并重命名了一些变量,您应该查看full sourceAPI 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()。每个入口点函数都遵循类似的策略,看起来类似于以下代码。

**注意:**和以前一样,这是一个简化的版本。详细信息请参见完整的源代码和API参考。

export function getFirestore(app?: FirebaseApp) {
    app = app || getApp(); // use given app or use default
    return app._providers.get('firestore') || initializeFirestore(app, DEFAULT_SETTINGS)
}

相关问题