【问题标题】:Collection object returned as undefined using async/await使用 async/await 返回未定义的集合对象
【发布时间】:2019-10-05 12:15:12
【问题描述】:

我正在设置一个快速应用程序来处理我的反应应用程序的 SSR。我需要访问我的一条路由上的 Firebase 内容,该路由在我的 routes.js 文件中定义为布尔值。

export default [
  {
    component: Home,
    path: '/',
    exact: true,
    fetchInitialData: true
  },
  {
    component: About,
    path: '/about',
    fetchInitialData: false
  }
];

这是通过我的 express 应用程序作为普通服务器端应用程序导入的,以确定路由是否将该布尔值设置为 true,然后我可以将函数的结果作为道具传递给我的应用程序的组件。

app.get('*', async (req, res) => {
//Get the matching route object for the URL
  const activeRoute = routes.find((route) => matchPath(url.parse(req.url).pathname, route)) || {}

  let interaction = {}
  const context = {};
  const modules = [];

//If the route object has a fetchInitialData boolean as truthy, set interaction to the result of getDownloadURL
  if(activeRoute.fetchInitialData){
    interaction = await getDownloadURL(req.query.id)
  }

  //THIS IS UNDEFINED, despite using async/await.
  console.log(interaction)

  //Interaction should be set from here. Process rest of app logic

我在使用 firebase 的 API 时遇到了障碍,因为我无法在我的 react 应用程序中包含 firebase-admin SDK。因此,我必须在 express 环境中运行它并将数据作为道具传递,因此我使用布尔值作为所需数据而不是将函数直接传递到应用程序的原因。 我不能使用普通的 firebase-sdk,因为我需要使用存储 getDownloadURL 函数,该函数使用与服务器不兼容的 XMLHttpRequest,仅与浏览器兼容!

我在 express 应用程序文件中定义了自己的 getDownloadURL 函数,该函数将查询参数作为文档 ID,并将处理存储在该文档中的图像的 downloadURL。

const getDownloadURL = (path) => {
  if(path){
    firestore.collection('interactions').doc(path).get()
      .then(doc => {
        bucket
        .file(doc.data().photoPath)
        .getSignedUrl({
          action: 'read',
          expires: '03-09-2999'
        })
        .then(photoURL => {  
          const interaction = {
            date: doc.data().date,
            venue: doc.data().venue,
            photoURL: photoURL
          }
          //console.log(interaction) <<<<< this returns the valid data.
          return interaction;     
        }).catch(e => console.log(e));
      }).catch(e => console.log(e));
  }else{
    return {};
  }
}

这将处理 firebase 文档并最终得到来自存储 API 的 photoURL,我将其作为对象返回给调用者。

我尝试按照本指南中的结构使用 Promises 而不是 async/await:https://tylermcginnis.com/react-router-server-rendering

  const activeRoute = routes.find((route) => matchPath(url.parse(req.url).pathname, route)) || {}

  const promise = activeRoute.fetchInitialData
    ? getDownloadURL(req.query.id)
    : Promise.resolve()

  promise.then((data) => {
  //Rest of app logic, data is still undefined

我没有从函数中返回对象,而是返回了 firebase 存储承诺并处理了来自调用者的承诺。

const getDownloadURL = (path) => {
  if(path){
    firestore.collection('interactions').doc(path).get()
      .then(doc => {
        return bucket
          .file(doc.data().photoPath)
          .getSignedUrl({
            action: 'read',
            expires: '03-09-2999'
          })
      }).catch(e => console.log(e));
  }else{
    return {};
  }
}

但是,此函数的结果始终未定义,我现在不确定如何修复它。任何帮助将非常感激。谢谢。

【问题讨论】:

  • 你永远不会返回firebase.collection....getDownloadUrl 中创建的承诺。
  • 如果我仍然需要访问该承诺中的数据,我将如何返回它,例如照片网址?我可以在调用者中处理这些数据吗?
  • 只需在firestore.collection... 之前添加return。确保它在同一行。您可能还想修改您的 else 语句以返回 Promise.resolve({}) 看起来您的其余代码将按原样工作。如果不是,您可能需要仔细检查 activeRoute.fetchInitialData 是否是您所期望的。
  • 如果你的 Promise 解析为另一个 Promise,那么你也需要在该 Promise 上调用 then。尝试使用调试器,或者只是 console.log 你在每个 then 块中获得的数据以查看它是什么类型的数据。
  • 您好,感谢您的帮助。我发表了一篇关于我的更改的帖子,我让它与你所说的一致,但也许我的解决方案有点笨拙?您可能会采取什么方式来优化它?

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


【解决方案1】:

谢谢@KhauriMcClain,我按照你所说的做了一些代码重构并让它工作,我最终处理了来自调用者的承诺,但我能够获得我需要的应用程序所需的数据,这是完美的。需要注意的一点是,我必须执行我的渲染逻辑,因为交互只在 .then 的范围内定义,我不想复制和粘贴我的代码。

app.get('*', async (req, res, next) => {
  const activeRoute = routes.find((route) => matchPath(url.parse(req.url).pathname, route)) || {}

  let interaction = {}  

  //If the route object has a fetchInitialData boolean as truthy
  // set interaction to the result of getDownloadURL
  if(activeRoute.fetchInitialData && req.query.id){
    interaction = await getDownloadURL(req.query.id)
      .then(doc => {
        bucket
        .file(doc.data().photoPath)
        .getSignedUrl({
          action: 'read',
          expires: '03-09-2999'
        })
        .then(photoURL => { 
          interaction = {
            date: doc.data().date,
            venue: doc.data().venue,
            photoURL: photoURL
          }          
          renderApp(req, res, interaction);
        }).catch(next)
      }).catch(next)
  }else{
    renderApp(req, res, interaction);
  }
})

getDownloadURL 现在就是这个

const getDownloadURL = (path) => {
  if(path){
    return firestore.collection('interactions').doc(path).get()
  }else{
    return Promise.resolve({});
  }
}

有没有更好的方法来处理这个问题?这似乎是一个相当笨拙的解决方案,因为我必须将 req 和 res 传递给另一个函数来处理我的渲染逻辑。

【讨论】:

    【解决方案2】:

    您可能会采取什么方式来优化它?

    async-awaitthen-catch 混用不利于可读性。如果您能提供帮助,您可能需要进一步重构,以便您的代码仅使用 async-await

    我不能保证以下代码会按原样运行,但它应该可以帮助您很好地了解在此处可以做什么来稍微重构您的代码。

    1) 获取 Firestore 文档的代码并不太冗长,而且您似乎需要在 getDownloadURL 函数之外的文档,因此您可以考虑将其移至主函数:

      if(activeRoute.fetchInitialData && req.query.id){
        const snap = await firestore.collection('interactions').doc(req.query.id).get()
        const data = snap.data()
        // ... 
    

    2) 你可以将getDownloadURL 设为异步函数,而不必担心返回承诺。异步函数隐式返回 Promise。此外,getDownloadURL 可以接收 photoPath,获取 photoURL,然后返回它,而不是获取文档路径。

    const getDownloadURL = async (photoPath) => {
      if(!photoPath){
        return {}
      }
      const photoURL = await bucket.file(photoPath)
        .getSignedUrl({
          action: 'read',
          expires: '03-09-2999'
        })
      return photoURL // assuming here that photoURL is an object
    }
    

    3) 最后,您可以用一些 async-await 替换其余代码。我也简化了这里的一些逻辑。

    app.get('*', async (req, res, next) => {
      const activeRoute = routes.find((route) => matchPath(url.parse(req.url).pathname, route)) || {}
    
      let interaction = {}  
    
      //If the route object has a fetchInitialData boolean as truthy
      // set interaction to the result of getDownloadURL
      if(activeRoute.fetchInitialData && req.query.id){
        try {
          const snap = await firestore.collection('interactions').doc(req.query.id).get()
          const data = snap.data()
          const photoURL = await getDownloadURL(data.photoPath)
          interaction = {
            date: data.date,
            venue: data.venue,
            photoURL: photoURL
          }   
        } catch (error) {
          return next(error) // return is important here
        }       
      }
      renderApp(req, res, interaction);
    })
    

    我认为您的另一个选择是将 getDownloadURL 重命名为 getInteraction 并让它返回完整的交互,而不仅仅是 URL,但这取决于您。

    这似乎是一个相当笨拙的解决方案,因为我必须将 req 和 res 传递给另一个函数来处理我的渲染逻辑。

    我个人认为这没有什么问题。

    【讨论】:

    • 太棒了,感谢您的帮助。最后通过使用它来工作,它更容易阅读,所以谢谢。
    猜你喜欢
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-05
    • 1970-01-01
    • 2017-06-09
    • 2020-04-21
    相关资源
    最近更新 更多