从Mongo中通过Express获取数据,构建对象并发送到React

3

我目前陷入了异步地狱。 在我的React应用中,我有一个名为/menu的页面,它通过expressjs API从mongo实例加载数据。

在我的名为menu的数据库中,我有代表每餐类型(例如"早餐"、"午餐"等)的集合。在这些集合中,每个项目的文档看起来像这个面包集合示例:

{
  _id: 2398jcs9dn2f9f,
  name: "Ciabatta",
  desc: "Italian bread",
  imageURI: "image01.jpg",
  reviews: []
}

这是我的 API,在页面加载时会被调用。
exports.getAllFoods = (req, res, next) => {
    const db = mongoose.connection

    const allCollections = {}

    try {
        db.db.listCollections().toArray((err, collections) => {
            collections.forEach((k) => {
                allCollections[k.name] = []
            })

            Object.keys(allCollections).map(k => {
                let Meal = mongoose.model(k, MealSchema)
            
                meal = Meal.find((err, docs) => {
                    allCollections[k] = docs
                    console.log(allCollections)
                })
            })
            res.send(allCollections)
        })
    } catch (error) {
        console.log(error)
        res.send('unable to get all collections')
    }
}

console.log(allCollections) 的最后一个输出结果如下:

{ snacks:
   [ { review: [],
       tags: [],
       _id: 5fcec3fc4bc5d81917c9c1fe,
       name: 'Simosa',
       description: 'Indian food',
       imageURI: 'image02.jpg',
       __v: 0 } ],
  breads:
   [ { review: [],
       tags: [],
       _id: 5fcec41a4bc5d81917c9c1ff,
       name: 'Ciabatta',
       description: 'Italian bread',
       imageURI: 'image02.jpg',
       __v: 0 } ],
}

这正是我需要的,但我卡在了如何发送到React上。我该怎么做才能发送上述json呢?res.send(allCollections) 给我的结果是:

{
    "snacks": [],
    "breads": [],
    "drinks": []
}

我明白为什么会发送上述内容,但我不知道该怎么做来解决它。

这是我的React页面加载时的情况。

useEffect(() => {
        axios
        .get('http://localhost:8888/api/allFoods')
        .then((res) => {
            setMealTypes(res.data)
        })
        .catch((err) => [
            console.log(err)
        ])
    }, [])

最终,我需要在控制台输出JSON,因为我想循环遍历该数据,并使用键作为标题,然后从值数组中列出值。
<div>
  <h2>Breads</h2>
  <img src=image01.jpg/>
  <h3>Ciabatta</h3>
  <p>Italian bread</p>
  ...
</div> 
...

我很感激任何帮助,以及任何可以帮助我提高JavaScript理解的文档。

3个回答

1
我更喜欢使用async/awaitPromise.all解决问题,以替换大部分回调函数。
因为你在遍历数组时调用了DB,所以你有最烦人的回调情况:如何发出一堆异步操作,然后在发送结果之前获取所有回调。你需要其他东西来确保所有回调都被调用。 Async/await意味着我们可以声明一个函数是异步的,并等待异步操作的结果。在JS中,异步/等待很麻烦,因为它抽象掉了回调并实际上在创建一个Promise。更进一步复杂化的是,异步/等待无法解决发出多个异步函数的问题,因此我们必须依靠这个花哨的Promise.all()函数,将期望的输入数组映射到异步函数上。
Object.keys(allCollections).map(k => {
  let Meal = mongoose.model(k, MealSchema)
  meal = Meal.find((err, docs) => {
    allCollections[k] = docs
    console.log(allCollections)
  })
});

建议使用异步/等待:
await Promise.all(Object.keys(allCollections).map(async k => {
  let Meal = mongoose.model(k, MealSchema)
  let docs = await Meal.find();
  allCollections[k] = docs;
  console.log(allCollections);
}));

另一个优点是错误处理。如果在原始示例的回调函数中发生任何错误,它们将不会在此try/catch块中被捕获。async/await处理错误的方式就像您期望的那样,错误将最终出现在catch块中。
...
      // Now that we have awaited all async calls above, this should be executed _after_ the async calls instead of before them.
      res.send(allCollections);
    })
  } catch (error) {
    console.log(error)
    res.send('unable to get all collections')
  }
}

从技术上讲,Promise.all()返回一个结果数组,但我们可以忽略它,因为你正在格式化一个Object

还有很大的优化空间。我可能会将整个函数编写成以下内容:

exports.getAllFoods = async (req, res, next) => {
  const db = mongoose.connection.db;

  try {
    let collections = await db.listCollections().toArray();

    let allCollections = {};
    collections.forEach((k) => {
      allCollections[k.name] = [];
    })

    // For each collection key name, find docs from the database
    // await completion of this block before proceeding to the next block
    await Promise.all(Object.keys(allCollections).map(async k => {
      let Meal = mongoose.model(k, MealSchema)
      let docs = await Meal.find();
      allCollections[k] = docs;
    }));

    // allCollections should be populated if no errors occurred
    console.log(allCollections);
    res.send(allCollections);
  } catch (error) {
    console.log(error)
    res.send('unable to get all collections')
  }
}

完全没有测试过。

你可能会发现这些链接比我的解释更有帮助:

https://javascript.info/async-await

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

https://medium.com/dailyjs/the-pitfalls-of-async-await-in-array-loops-cf9cf713bfeb


非常感谢。这解决了我的问题。正如你所说,这允许所有回调在发送我的请求之前完成。非常感谢。我将查看这些资源以更好地理解异步/等待。 - i_cant_code

0

希望这能帮到你:在从Express API发送集合之前,您需要先使用stringify方法,然后在React前端使用JSON.parse来恢复对象。 附注:您能否在res.send(allCollections)的上一行执行console.log(allCollections)?


0

你需要以 JSON 格式将其发送到前端。

res.json(allCollections) 替换 res.send(allCollections)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接