首页 > 解决方案 > 如何使猫鼬更新与等待一起工作?

问题描述

我正在创建一个 NodeJS 后端,其中一个进程从源读取数据,检查与当前数据相比的更改,对 MongoDB 进行这些更新并报告所做的更改。一切正常,除了我无法报告更改,因为我无法等待 Mongoose 更新操作。

这个函数返回的数组然后由 Koa 服务器显示。它显示一个空数组,并且在服务器日志中,服务器返回空响应后会出现正确的值。

我已经深入研究了 Mongoose 文档和 Stack Overflow 问题——关于这个主题的很多问题——但没有成功。提供的解决方案似乎都没有帮助。我已将问题隔离到这一部分:如果我删除 Mongoose 部分,一切都会按预期工作。

const parseJSON = async xmlData => {
    const changes = []

    const games = await Game.find({})
    const gameObjects = games.map(game => {
        return new GameObject(game.name, game.id, game)
    })

    let jsonObj = require("../sample.json")
    Object.keys(jsonObj.items.item).forEach(async item => {
        const game = jsonObj.items.item[item]
        const gameID = game["@_objectid"]
        const rating = game.stats.rating["@_value"]
        if (rating === "N/A") return
        const gameObject = await gameObjects.find(
            game => game.bgg === parseInt(gameID)
        )
        if (gameObject && gameObject.rating !== parseInt(rating)) {
            try {
                const updated = await Game.findOneAndUpdate(
                    { _id: gameObject.id },
                    { rating: rating },
                    { new: true }
                ).exec()
                changes.push(
                    `${updated.name}: ${gameObject.rating} -> ${updated.rating}`
                )
            } catch (error) {
                console.log(error)
            }
        }
    })

    return changes
}

一切正常——发现更改并更新数据库,但报告的更改返回得太晚,因为执行不等待 Mongoose。

我也试过这个而不是findOneAndUpdate()

const updated = await Game.findOne()
    .where("_id")
    .in([gameObject.id])
    .exec()
updated.rating = rating
await updated.save()

这里的结果相同:其他一切都有效,但异步没有。

标签: node.jsmongodbmongooseasync-await

解决方案


map正如@Puneet Sharma 所提到的,您必须forEach先获得一系列承诺,然后await在返回之前获得承诺(Promise.all为了方便起见)changes,然后将其填充:

const parseJSON = async xmlData => {
    const changes = []

    const games = await Game.find({})
    const gameObjects = games.map(game => {
        return new GameObject(game.name, game.id, game)
    })

    const jsonObj = require("../sample.json")
    const promises = Object.keys(jsonObj.items.item).map(async item => {
        const game = jsonObj.items.item[item]
        const gameID = game["@_objectid"]
        const rating = game.stats.rating["@_value"]
        if (rating === "N/A") return
        const gameObject = await gameObjects.find(
            game => game.bgg === parseInt(gameID)
        )
        if (gameObject && gameObject.rating !== parseInt(rating)) {
            try {
                const updated = await Game.findOneAndUpdate(
                    { _id: gameObject.id },
                    { rating: rating },
                    { new: true }
                ).exec()
                changes.push(
                    `${updated.name}: ${gameObject.rating} -> ${updated.rating}`
                )
            } catch (error) {
                console.log(error)
            }
        }
    })

    await Promise.all(promises)
    return changes
}

(差异,为方便起见:

9,10c9,10
<     let jsonObj = require("../sample.json")
<     Object.keys(jsonObj.items.item).forEach(async item => {
---
>     const jsonObj = require("../sample.json")
>     const promises = Object.keys(jsonObj.items.item).map(async item => {
33a34
>     await Promise.all(promises)

)


编辑:进一步的重构是使用该系列的承诺来进行更改描述本身。基本上changePromises是一个Promise解析为字符串或 null 的数组(如果没有更改),因此.filter带有标识函数的 a 将过滤掉虚假值。

此方法还有一个优点,即changes与键的迭代顺序相同;使用原始代码,无法保证顺序。这对您的用例可能很重要,也可能无关紧要。

我还翻转了map函数中的 if/else 以减少嵌套;这真的是品味问题。

附言。await Game.find({})当您拥有大量游戏时,这将是一个问题。

const parseJSON = async xmlData => {
  const games = await Game.find({});
  const gameObjects = games.map(game => new GameObject(game.name, game.id, game));

  const jsonGames = require("../sample.json").items.item;

  const changePromises = Object.keys(jsonGames).map(async item => {
    const game = jsonGames[item];
    const gameID = game["@_objectid"];
    const rating = game.stats.rating["@_value"];
    if (rating === "N/A") {
      // Rating from data is N/A, we don't need to update anything.
      return null;
    }
    const gameObject = await gameObjects.find(game => game.bgg === parseInt(gameID));
    if (!(gameObject && gameObject.rating !== parseInt(rating))) {
      // Game not found or its rating is already correct; no change.
      return null;
    }
    try {
      const updated = await Game.findOneAndUpdate(
        { _id: gameObject.id },
        { rating: rating },
        { new: true },
      ).exec();
      return `${updated.name}: ${gameObject.rating} -> ${updated.rating}`;
    } catch (error) {
      console.log(error);
    }
  });

  // Await for the change promises to resolve, then filter out the `null`s.
  return (await Promise.all(changePromises)).filter(c => c);
};

推荐阅读