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

Posted

技术标签:

【中文标题】Firestore 交叉引用返回一个承诺对象,无法访问文档值【英文标题】:Firestore cross-reference returning a promise object, can't access document value 【发布时间】:2020-08-25 18:13:11 【问题描述】:

场景

我的应用是使用 React 和 Firebase 构建的。该应用程序中有一项功能允许用户创建播放列表并用目录中的曲目填充它们。播放列表有一个名为“Tracks”的子集合,用于存储曲目 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() 记录到控制台,它会显示我最终需要的值。有没有办法按照编写代码的方式返回这些值?我尝试了不同的方式来编写 async/await 函数,并且我还尝试返回一个Promise.resolve(data),结果总是相同的。 我最接近我希望的行为是通过创建一个函数,我可以在其中抓取 Track 并将其推送到处于状态的轨道数组,例如:

// @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】:

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();
            )
          ;
        ));

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

      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);
      );

【讨论】:

太棒了,我只需要在内部块中添加 async + await Promise.resolve() 就可以了。

以上是关于Firestore 交叉引用返回一个承诺对象,无法访问文档值的主要内容,如果未能解决你的问题,请参考以下文章

在 Firestore 的集合中删除所有文档时返回所有文档

嵌套的 Javascript 承诺 - 从 Firestore 获取数据

使用 Swift (Firestore) 访问特定文档字段

从 Firestore 中检索文档引用的值

返回承诺链中的引用数据

React setState未在firestore中执行添加文档承诺解析