Почему Firestore 'do c .get (' time '). ToMillis' выдает ошибку нулевого типа? - PullRequest
3 голосов
/ 19 марта 2020

В приложении с естественным взаимодействием я вызываю действие, отправляющее данные в 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), который заполняет хранилище избыточности сообщениями от Коллекция в пожарном магазине. слушатель снимка выглядит следующим образом:

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 в том же компоненте экрана, который выглядит следующим образом:

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, и эта задержка вызывает нулевую инициализацию значения времени, но его достаточно долго, чтобы обработать sh приложение.

Вопрос: что здесь на самом деле происходит за кулисами и как я могу предотвратить эту ошибку?

1 Ответ

2 голосов
/ 26 апреля 2020

Проблема, с которой вы столкнулись, вызвана тем, что прослушиватель onSnapshot() запускается из локального кэша Firestore во время небольшого окна, где значение 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 перед игнорированием события, чтобы при первой загрузке ар p, он немедленно распечатает любую кэшированную информацию. Без этого вы будете показывать пустой список сообщений, пока сервер не ответит. Большинство сайтов будут распечатывать любые кэшированные данные, показывая пульсатор в верхней части страницы, пока сервер не ответит новыми данными.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...