【问题标题】:Why is Firestore's 'doc.get('time').toMillis' producing a null Type Error?为什么 Firestore 的 'doc.get('time').toMillis' 会产生空类型错误?
【发布时间】:2020-06-30 04:22:18
【问题描述】:

在一个 react-native 应用程序中,我调用一个动作将数据发送到 firebase。但是在通话之后,我收到一个错误,其来源似乎与快照侦听器的工作方式有关。 add-msg 操作或多或少看起来像这样:

创建数据:

const addMsg = (msg, convoIds) => {
    console.log('Firestore Write: (actions/conversation) => addMsg()');

    return firebase.firestore().collection('messages').add({
        time: firebase.firestore.FieldValue.serverTimestamp(),
        sender: msg.sender,
        receiverToken: msg.receiverToken,
        sessionId: msg.sessionId,
        read: false,
        charged: false,
        userConvos: [ convoIds.sender, convoIds.receiver ],
        content: {
            type: 'msg',
            data: msg.text
        }
    });
};

我还有一个快照监听器(在 componentDidMount 中执行),它使用来自 firestore 中的集合的消息填充 redux 存储。快照监听器看起来像:

export const getMessages = (convoId) => {
    const tmp = convoId == null ? '' : convoId;
    return (dispatch) => {
        console.log('Firestore Read (Listener): (actions/conversation) => getMessages()');
        return new Promise((resolve, reject) => {
            firebase
                .firestore()
                .collection('messages')
                .where('userConvos', 'array-contains', tmp)
                .orderBy('time')
                .onSnapshot((querySnapshot) => {
                    const messages = [];
                    querySnapshot.forEach((doc) => {
                        const msg = doc.data();
                        msg.docId = doc.id;
                        msg.time = doc.get('time').toMillis();

                        messages.push(msg);
                    });
                    dispatch({ type: types.LOAD_MSGS, payload: messages });
                    resolve();
                });
        });
    };
};

在同一屏幕组件中填充 flatList 的相应 reducer,如下所示:

const INITIAL_STATE = {
    messages: []
};
export default (state = INITIAL_STATE, action) => {
    switch (action.type) {
        case types.LOAD_MSGS:
            return {
                messages: action.payload
            };
        default:
            return { ...state };
    }
};

问题:一旦数据发送,我立即收到错误TypeError: null is not an object (evaluating 'doc.get('time').toMillis'。如果我重新加载应用程序,导航回有问题的屏幕,就会出现消息,时间数据也会出现。所以我的猜测是firebase调用的承诺性质的延迟正在发生一些事情,并且延迟导致时间值的空初始化,但它的时间足以使应用程序崩溃。

问题:这里的幕后实际发生了什么,我该如何防止这个错误?

【问题讨论】:

  • doc.get('time') 是否为空?
  • @LajosArpad 当我在屏幕组件中访问它时,它会显示时间
  • 如果我是你,我会在msg.time = doc.get('time').toMillis(); 行之前以console.log(doc.get('time').toMillis()) 的形式做一个实验。你会看到那是否是空的东西。这是您解决问题所需的关键信息。
  • @LajosArpad 已经有了。所有时间都被记录
  • 您能否逐字引用您收到的问题的确切错误消息?

标签: javascript firebase react-native redux react-redux


【解决方案1】:

您遇到的问题是由本地 Firestore 缓存中的 onSnapshot() 侦听器在一个小窗口期间触发的,其中 firebase.firestore.FieldValue.serverTimestamp() 的值被视为待处理,默认情况下被视为 null。一旦服务器接受您的更改,它会以时间戳的所有新值进行响应,并再次触发您的 onSnapshot() 侦听器。

如果不小心,这可能会导致您的应用“闪烁”,因为它会两次标记数据。

要更改待处理时间戳的行为,您可以将SnapshotOptions 对象作为最后一个参数传递给doc.data()doc.get()(视情况而定)。

以下代码指示 Firebase SDK 根据本地时钟估计新的时间戳值。

const estimateTimestamps = {
  serverTimestamps: 'estimate'
}

querySnapshot.forEach((doc) => {
  const msg = doc.data(); // here msg.time = null
  msg.docId = doc.id;
  msg.time = doc.get('time', estimateTimestamps).toMillis(); // update msg.time to set value (or estimate if not available)

  messages.push(msg);
});

如果您想显示您的消息仍在写入数据库,您可以在估计时间戳之前检查msg.time 是否为null

const estimateTimestamps = {
  serverTimestamps: 'estimate'
}

querySnapshot.forEach((doc) => {
  const msg = doc.data(); // here msg.time = null when pending
  msg.docId = doc.id;
  msg.isPending = msg.time === null;
  msg.time = doc.get('time', estimateTimestamps).toMillis(); // update msg.time to set value (or estimate if not available)

  messages.push(msg);
});

如果您想忽略这些中间“本地”事件以等待服务器的完整响应,您可以使用:

.onSnapshot({includeMetadataChanges: true}, (querySnapshot) => {
  if (querySnapshot.metadata.fromCache && querySnapshot.metadata.hasPendingWrites) {
    return; // ignore cache snapshots where new data is being written
  }
  const messages = [];
  querySnapshot.forEach((doc) => {
    const msg = doc.data();
    msg.docId = doc.id;
    msg.time = doc.get('time', estimateTimestamps).toMillis();

    messages.push(msg);
  });
  dispatch({ type: types.LOAD_MSGS, payload: messages });
  resolve();
});

在上面的代码块中,注意我在忽略事件之前还检查了querySnapshot.metadata.hasPendingWrites,这样当你第一次加载你的应用程序时,它会立即打印出所有缓存的信息。如果没有这个,您将显示一个空的消息列表,直到服务器响应。大多数网站会打印出所有缓存的数据,同时在页面顶部显示一个 throbber,直到服务器响应任何新数据。

【讨论】:

  • 作为补充说明,不要忘记正确处置您的听众!对onSnapshot() 的调用会返回一个取消订阅函数,您应该在释放组件时存储和调用该函数。例如const unsubMessageListener = firebase...onSnapshot(...); 和处置时调用 unsubMessageListener()
  • 非常有见地的信息。但是在尝试了列出的所有三个选项后,我仍然遇到同样的错误
  • 这听起来像 time 字段在您的集合中的 1 个或多个文档上丢失/设置为 null,这可能是由于将 messages 数组中的条目保存回数据库而没有正确的时间戳(因此 msg.time 现在将是 null),例如在编辑消息时。试试这个而不是 msg.time = ... 行:const timestamp = doc.get('time', estimateTimestamps); if (timestamp) { msg.time = timestamp.toMillis(); } else { console.error(doc.id + ' is missing "time" field!'); /* debugger; */ }。如果您成功了,请取消注释 debugger; 并仔细查看。
  • 是的,else条件被执行了,但是当我检查相关文档时,“时间”字段在那里?
  • 生病奖励赏金,因为你是唯一需要时间提供帮助的人,即使我还没有修复错误:'(
猜你喜欢
  • 1970-01-01
  • 2021-12-01
  • 2018-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-26
  • 2011-01-27
相关资源
最近更新 更多