为啥 Firestore 的 'doc.get('time').toMillis' 会产生空类型错误?
Posted
技术标签:
【中文标题】为啥 Firestore 的 \'doc.get(\'time\').toMillis\' 会产生空类型错误?【英文标题】: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 已经有了。所有时间都被记录
您能否逐字引用您收到的问题的确切错误消息?
【参考方案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条件被执行了,但是当我检查相关文档时,“时间”字段在那里?
生病奖励赏金,因为你是唯一需要时间提供帮助的人,即使我还没有修复错误:'(以上是关于为啥 Firestore 的 'doc.get('time').toMillis' 会产生空类型错误?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Firebase 数据库(Firestore)不添加文档