在从 firebase 收集数据之前呈现网页 - NodeJS 和 EJS

Posted

技术标签:

【中文标题】在从 firebase 收集数据之前呈现网页 - NodeJS 和 EJS【英文标题】:Webpage is rendering before data is collected from firebase - NodeJS and EJS 【发布时间】:2020-07-23 21:56:48 【问题描述】:

我尝试过使用 async-await、.then 和 now promise。我是javascript开发的新手。

代码

indexRouter.get('/dashboard', checkSignIn, async(request, response) => 
    snapshot = await db.doc('users/accounts').get()
    sites = snapshot.data()['sites']
    const userId = request.session.userId
    snapshot = await db.doc(`users/$userId`).get()
    var linkedSites = snapshot.data()['linked sites']
    let getDs = new Promise((resolve,reject)=>
        console.log("1")
            linkedSites.forEach((site) =>
                console.log("2")
            db.doc(`users/$userId`).collection(site).get()
            .then((snapshot)=>
                console.log("3")
                snapshot.forEach((doc) => 
                console.log("4")
                console.log(doc.id)
                emailId = doc.id
                keys = doc.data()['keys']
                var passwordEncrypt = doc.data()['password']
                password = cryptoJS.....
                details.push(site:'email': emailId, 'password': password, 'keys': keys)
                )
            )
        )
        console.log("5")
        console.log(details)
        resolve(details)
        

    );

    getDs.then((details)=>
        console.log("rendering")
        response.render('dashboard', 'details':details, 'linkedSites': linkedSites, 'sites': sites)
    )

我正在收到回复

1
2
2
5
[]
rendering
error: ...details not found in ejs
3
4
rsp121@gmail.com
3
4
test@gmail.com

根据输出,console.log(2) 之后的 db.doc 行似乎在一定时间后被执行,并且之前发送了 resolve(details)。


找到解决问题的方法:

indexRouter.get('/dashboard', checkSignIn, async(request, response) => 
    snapshot = await db.doc('users/accounts').get()
    sites = snapshot.data()['sites']
    const userId = request.session.userId
    snapshot = await db.doc(`users/$userId`).get()
    var linkedSites = snapshot.data()['linked sites']
    if(linkedSites)
        const getSnapshot = (site) => 
            return new Promise(resolve => 
                db.doc(`users/$userId`).collection(site).get()
                .then((snapshot) =>
                    snapshot.forEach((doc) =>
                        emailId = doc.id
                        keys = doc.data()['keys']
                        var passwordEncrypt = doc.data()['password']
                        password = cryptoJS
                        details[site] = 'email': emailId, 'password': password, 'keys': keys
                        resolve(true)
                    )
                )
            )
        

        Promise.all(linkedSites.map(getSnapshot)).then(()=>
            console.log(linkedSites)
            response.set('Cache-Control', 'no-store, no-cache, must-revalidate, private')
            response.render('dashboard', 'details':details, 'linkedSites': linkedSites, 'sites': sites)
        )
    

【问题讨论】:

【参考方案1】:

问题是你的承诺在db.doc 被解决之前解决了,因为你的 db.doc 承诺在一个循环中。所以,你应该使用promise.all 下面的代码应该适合你。

indexRouter.get("/dashboard", checkSignIn, async (request, response) => 
  snapshot = await db.doc("users/accounts").get();
  sites = snapshot.data()["sites"];
  const userId = request.session.userId;
  snapshot = await db.doc(`users/$userId`).get();
  var linkedSites = snapshot.data()["linked sites"];
  let getDs = new Promise((resolve, reject) => 
    const promises = [];
    console.log("1");
    linkedSites.forEach((site) => 
      console.log("2");
      promises.push(
        new Promise((internalResolve) => 
          db.doc(`users/$userId`)
            .collection(site)
            .get()
            .then((snapshot) => 
              console.log("3");
              snapshot.forEach((doc) => 
                console.log("4");
                console.log(doc.id);
                emailId = doc.id;
                keys = doc.data()["keys"];
                var passwordEncrypt = doc.data()["password"];
                password = cryptoJS;
                details.push(
                  site: 
                    email: emailId,
                    password: password,
                    keys: keys,
                  ,
                );
                internalResolve();
              );
            );
        )
      );
    );
    Promise.all(promises).then(() => 
      console.log("5");
      console.log(details);
      resolve(details);
    );
  );
  getDs.then((details) => 
    console.log("rendering");
    return response.render("dashboard", 
      details: details,
      linkedSites: linkedSites,
      sites: sites,
    );
  );
);

async/await 更简洁。

indexRouter.get("/dashboard", checkSignIn, async (request, response) => 
  snapshot = await db.doc("users/accounts").get();
  sites = snapshot.data()["sites"];
  const userId = request.session.userId;
  snapshot = await db.doc(`users/$userId`).get();
  var linkedSites = snapshot.data()["linked sites"];
  console.log("1");
  linkedSites.forEach(async (site) => 
    console.log("2");
    const snapshot = await db.doc(`users/$userId`).collection(site).get();
    console.log("3");
    snapshot.forEach((doc) => 
      console.log("4");
      console.log(doc.id);
      emailId = doc.id;
      keys = doc.data()["keys"];
      var passwordEncrypt = doc.data()["password"];
      password = cryptoJS;
      details.push(
        site: 
          email: emailId,
          password: password,
          keys: keys,
        ,
      );
    );
  );
  console.log("rendering");
  return response.render("dashboard", 
    details: details,
    linkedSites: linkedSites,
    sites: sites,
  );
);

【讨论】:

Async/Await 代码仍然导致同样的错误。承诺代码就像一个魅力。我不知道为什么 async-await 不起作用。它适用于代码的其他部分。 这很奇怪。【参考方案2】:

这是您的代码已更正、优化并使用 ECMA 脚本的最后一个 especifications,错误是正如其他评论所说,您无需等待“新 Promise..”声明中的承诺结果。如果您没有***错误处理程序,请不要在异步函数中使用 try/catch。

优化首先是快照变量,这样您就可以并行获取数据,而不是安全地获取数据。

indexRouter.get('/dashboard', checkSignIn, async (request, response) => 
try 
    let details = [];
    const userId = request.session.userId
    let [snapshot1, snapshot2] = await Promise.all([db.doc('users/accounts').get(), await db.doc(`users/$userId`).get()])
    let sites = snapshot1.data()['sites']
    var linkedSites = snapshot2.data()['linked sites'];

    await Promise.all(
        linkedSites.map((site) =>
            db.doc(`users/$userId`).collection(site).get()
                .then((snapshot) => 
                    snapshot.forEach((doc) => 
                        emailId = doc.id
                        keys = doc.data()['keys']
                        var passwordEncrypt = doc.data()['password']
                        password = cryptoJS
                        details.push( site:  'email': emailId, 'password': password, 'keys': keys  )
                    )
                )
        )
    )

    response.render('dashboard',  'details': details, 'linkedSites': linkedSites, 'sites': sites )
 catch (e) 
    //Render error you want
)

linkedSites.map.... 返回一个 Promise 数组,这些 Promise 最终被包装在 Promise.all 中,Promise.all 等到所有 Promise 都完成,或者在最后一种情况下其中一个被拒绝,你的代码会去捕获没有到达 try 内的 response.render 行。您可以使用

避免在本地捕获地图中每个承诺的错误
 .then((snapshot) => 
 ...
).catch(e=>  /*Do something with the rror*/)

【讨论】:

以上是关于在从 firebase 收集数据之前呈现网页 - NodeJS 和 EJS的主要内容,如果未能解决你的问题,请参考以下文章

在从另一个组件传入正确的prop数据之前呈现的组件

我的 flatList 在我的 api 查询完成之前呈现

如何仅在从 Firebase 下载数据后执行操作

if 语句在从 firebase 检索数据时未返回正确的元素

如何在演示之前更改远程通知的呈现方式?

在 forEach 之后使用 Promise.all() 渲染 NodeJS 页面之前等待 Firebase 数据加载