返回承诺对象的Firestore交叉引用无法访问文档值

时间:2020-05-10 07:49:32

标签: javascript reactjs firebase google-cloud-firestore async-await

场景

我的应用程序是使用React和Firebase构建的。该应用程序中有一项功能,允许用户创建播放列表,并用目录中的曲目填充它们。播放列表具有一个称为“曲目”的子集合,用于存储曲目ID和其他一些信息。查看播放列表时,我想遍历曲目并将它们交叉引用(如SQL中的联接)到Tracks集合以填充视图。

// Looks something like this before the return...

// Attempting to write an async/await function
async function getTrackDocument(trackId) {
  const track = await firestore
    .collection("tracks")
    .doc(trackId)
    .get();
  return track;
}

useEffect(() => {
  const playlistRef = firestore.doc(`playlists/${playlistId}`);
  const playlistTracksRef = firestore.collection(
    `playlists/${playlistId}/tracks`
  );

  playlistRef.get().then(doc => {
    if (doc.exists) {
      const unsubscribe = playlistTracksRef.onSnapshot(snapshot => {
        // Snapshot of playlist tracks
        const playlistTracks = snapshot.docs.map(doc => {
          return {
            id: doc.id,
            ...doc.data(),
            // Getting the track document
            trackRef: getTrackDocument(doc.id).then(doc => {
              return doc.data();
            })
          };
        });

        setTracks(playlistTracks);
      });

      return () => unsubscribe();
    }
  });
});

问题

我要交叉引用播放列表轨道ID到“轨道”集合的部分:

trackRef: getTrackDocument(doc.id).then(doc => {
   return doc.data();
})

我希望能为我提供所需的曲目信息。该部分在控制台中返回一个Promise对象:

trackRef: Promise
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Object
__proto__: Object

[[PromiseValue]]具有所有值,但似乎无法到达可以使用这些值的位置。我可以将doc.data()登录到控制台,它向我显示了最终需要的值。有没有办法以代码编写的方式内联地返回这些值?我尝试了不同的方式来编写异步/等待功能,并且还尝试返回Promise.resolve(data),始终保持相同的结果。

我最希望达到的行为是通过创建一个函数,在该函数中可以抓取Track并将其推入状态的tracks数组中,例如:

// @param arrayOfTrackIDs -- just track IDs from the playlist
function getTracksThenPopulateArray(arrayOfTrackIDs) {
   arrayOfTrackIDs.forEach(trackID => {
      firestoreTracks.doc(trackID).get().then(doc => setTracks(tracks => [...tracks, track])); 
   }
}

但是,这并不理想,因为如果删除或添加了轨道,我无法合并.onSnapshot()来更新视图。

解决方案

类似于用户Gazihan的建议,我使用了他的async / await Promise.all()并为内部文档添加了async / await Promise.resolve(),并且有效:

const unsubscribe = playlistTracksRef.onSnapshot(async snapshot => {
  const playlistTracks = await Promise.all(
    snapshot.docs.map(async doc => {
      return {
        id: doc.id,
        ...doc.data(),
        trackRef: await Promise.resolve(
          getTrackDocument(doc.id).then(doc => {
            return doc.data();
          })
        )
      };
    })
  );
  setTracks(playlistTracks);
});

1 个答案:

答案 0 :(得分:1)

map()已同步。使用map()只能获得Promise实例的列表。您必须等待它们中的每个使其成为具有实际值的列表。 Promesi.all()做到了。

这是固定代码:

        const playlistTracks = await Promise.all(snapshot.docs.map(doc => {
          return {
            id: doc.id,
            ...doc.data(),
            // Getting the track document
            trackRef: getTrackDocument(doc.id).then(doc => {
              return doc.data();
            })
          };
        }));

但是,要能够在await之前使用Promise.all(),您需要处于异步函数中。这是一个尝试,应该可以工作:

      const unsubscribe = playlistTracksRef.onSnapshot(async snapshot => {
        // Snapshot of playlist tracks
        const playlistTracks = await Promise.all(snapshot.docs.map(doc => {
          return {
            id: doc.id,
            ...doc.data(),
            // Getting the track document
            trackRef: getTrackDocument(doc.id).then(doc => {
              return doc.data();
            })
          };
        }));

        setTracks(playlistTracks);
      });