【问题标题】:Correct handling of async Mongo actions in Node Promise正确处理 Node Promise 中的异步 Mongo 操作
【发布时间】:2017-08-24 01:47:18
【问题描述】:

我有一个执行多个 Mongo 操作的函数,最后一个操作是在所有其他操作完成后关闭数据库。我对自己对问题的处理相当有信心,但我有一些外部 cmets 提出了担忧,我想验证我的解决方案是否正确。

建议的解决方案:

function updateDatabase (name, token) {
  return new Promise((resolve, reject) => {
    MongoClient.connect(MONGODB_URL)
      .then( (database) => {
          return database.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}})
            .then( () => {
              database.collection('log').insert({
                name,
                token
              });
              return database;
            })
      })
      .then( (db) => {
        db.close(true);
        resolve(`Invalid token: ${token} has been removed from: ${name}`);
      })
      .catch( (err) => {
        reject(err);
      })
  });
}

我原来的解决方案:

function updateDatabase (name, token) {
  return new Promise((resolve, reject) => {
    MongoClient.connect(MONGODB_URL)
      .then( (database) => {
        return database;
      })
      .then( (db) => {
        database.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}})
        return database;
      })
      .then( () => {
        database.collection('log').insert({
          name,
          token
        });
        return database;
      })
      .then( (db) => {
        db.close(true);
        resolve(`Invalid token: ${token} has been removed from: ${name}`);
      })
      .catch( (err) => {
        reject(err);
      })
  });
}

我的原始解决方案是否偏离了标准,还是建议的解决方案是更好的方法?两者都在测试中工作,但针对生产级负载,我需要确保在其他操作完成之前数据库不会关闭,我相信我已经在我的原始解决方案中完成了这一点。

【问题讨论】:

  • 您是在问两者之一是否会在完成操作之前关闭连接?如果是这样,如果你说他们都通过了你的测试,我认为他们不会,假设测试很好。还是您要进行风格比较?性能比较?
  • 两种实现都缺乏:使用new Promise 包装基于promise 的代码是一种反模式,没有适当的错误检查(尽管建议的解决方案是bit 更好),并且数据库不会在 catch 处理程序中关闭。
  • @TW80000 我问的是两者之一是否会在完成操作之前关闭连接?我的测试不是负载测试或性能测试,只是检查更新是否发生以及数据库是否关闭。
  • @robertklep,这是一个很好的观点。也许我应该删除 return new Promise,只执行 MongoClient 操作,这将返回一个 promise 对象?
  • @jmcgui05 我不是专家,但如果连接在更新完成之前关闭,那么确保更新发生的测试将会失败,对吧?既然他们没有失败,你不能断定连接在操作后关闭,因此你的代码工作正常吗?它在我看来当然是正确的(除了 robertklep 提到的包装承诺)。

标签: node.js mongodb es6-promise


【解决方案1】:

由于 MongoDB 驱动的所有异步操作都已经返回了一个 Promise,所以你根本不应该使用new Promise,而是设置一个 Promise 链:

function updateDatabase(name, token) {
  let database;
  return MongoClient.connect(MONGODB_URL).then(db => {
    database = db;
    return database
      .collection("testCollection")
      .update({ name }, { $pull: { tokens: { $in: [token] } } });
  })
  .then(() => {
    return database.collection("log").insert({
      name,
      token
    });
  })
  .then(() => {
    database.close(true);
  })
  .catch(err => {
    database.close(true);
    throw err;
  });
}

我了解您希望将 database 作为参数传递给下一个 then,但您会遇到在 catch 处理程序中不可用的问题。一种解决方案是使用在打开连接后分配的函数范围变量,就像上面的代码一样。

如果您不喜欢这样,您可以在 .then 处理程序中为 MongoClient.connect 创建一个新的承诺链:

function updateDatabase(name, token) {
  return MongoClient.connect(MONGODB_URL).then(database => {
    return database
      .collection("testCollection")
      .update({ name }, { $pull: { tokens: { $in: [token] } } })
      .then(() => {
        return database.collection("log").insert({
          name,
          token
        });
      })
      .then(() => {
        database.close(true);
      })
      .catch(err => {
        database.close(true);
        throw err;
      });
  });
}

【讨论】:

    【解决方案2】:

    我试图概述原始代码的一些主要问题:

    function updateDatabase (name, token) {
      //useless. MongoClient.connect already returns the promise
      return new Promise((resolve, reject) => {
        MongoClient.connect(MONGODB_URL)
          .then( (database) => {
            // useless. The function does nothing and can be removed
            return database;  
          })
          .then( (db) => {
            // should be db, not database
            // update is async. Should be added to the chain.
            database.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}})
            // what's the point to return database, if the following function does not accept any parameters
            return database;
          })      
          .then( () => {
            // insert is async. Should be added to the chain.
            database.collection('log').insert({
              name,
              token
            });
            return database;
          })
          .then( (db) => {
            // close is async. Should be added to the chain.
            db.close(true);
            resolve(`Invalid token: ${token} has been removed from: ${name}`);
          })
          .catch( (err) => {
            reject(err);
          })
      });
    }
    

    所以函数应该看起来像:

    function updateDatabase (name, token) {
        return MongoClient.connect(MONGODB_URL)
          .then( db => 
            db.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}})
              .then(()=>db)
          })      
          .then( db => 
            db.collection('log').insert({name, token})
              .then(()=>db)
          })
          .then( db => db.close(true))
          .then(()=>`Invalid token: ${token} has been removed from: ${name}`);
    }
    

    如果查询的顺序无关紧要,您可以从Promise.all 中受益:

    function updateDatabase (name, token) {
        return MongoClient.connect(MONGODB_URL)
          .then( db => Promise.all([
            Promise.resolve(db),
            db.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}}),
            db.collection('log').insert({name, token}),
          ])
          .then( ([db]) => db.close(true))
          .then(()=>`Invalid token: ${token} has been removed from: ${name}`);
    }
    

    【讨论】:

      【解决方案3】:

      这两种解决方案的共同问题是 .then(function) 处理程序。处理程序返回数据库,而不是为 MongoDB 操作返回 Promise,因此将调用下一个链处理程序,而不是等待 MongoDB 操作完成。相反,应按执行顺序链接 MongoDB 操作的 Promise。 Mongo 方法也返回 Promise,因此不需要新的 Promise:

      function updateDatabase (name, token) {
        return MongoClient.connect(MONGODB_URL)
           .then( (db) => 
                Promise.all([
                  db.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}}),
                  db.collection('log').insert({name, token})
                ]).then(() => db.close(true)
                ).then(`Invalid token: ${token} has been removed from: ${name}`
                ).catch((err) => { db.close(true); throw err });
           )
      }
      

      或使用 async/await 语法:

      async function updateDatabase (name, token) {
          let db = await MongoClient.connect(MONGODB_URL);
          try {
            await db.collection('testCollection').update({name}, {$pull: {tokens: {$in: [token]}}});
            await db.collection('log').insert({name, token});
            return `Invalid token: ${token} has been removed from: ${name}`;
          } finally { 
            await db.close(true);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-15
        • 2019-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多