使用async / await返回未定义的集合对象

时间:2019-05-18 11:55:48

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

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

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

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

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。因此,我必须在快速环境中运行它,并将数据作为道具传递下来,因此,原因是我对所需的数据使用布尔值,而不是将函数直接传递给应用程序。 我不能使用普通的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 {};
  }
}

但是,此函数的结果始终是不确定的,我不知道该如何解决。任何帮助将非常感激。谢谢。

2 个答案:

答案 0 :(得分:0)

谢谢@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传递给另一个函数来处理渲染逻辑。

答案 1 :(得分:0)

  

您是否可能会对此进行优化?

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成为异步函数,而不必担心返回承诺。异步函数隐式返回诺言。此外,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传递给另一个函数来处理渲染逻辑。

我个人认为这没有任何问题。