【问题标题】:javascript callback is called twice in an impossible invocationjavascript 回调在不可能的调用中被调用两次
【发布时间】:2019-06-11 01:39:12
【问题描述】:

我构建了一个 TS,MongoDB 客户端包装器。由于某种原因,当我调用获取连接的函数时,它的回调被调用了两次。

共有 2 次调用 get() 函数,1 次在导出之前,如您所见,另一个来自 mocha 测试。

总的来说,我对 TS 和 JS 很陌生,但这似乎有点不对劲。

    import {Db, MongoClient} from "mongodb";
    import {MongoConfig} from '../config/config'
    class DbClient {
        private cachedDb : Db = null;
        private async connectToDatabase() {
            console.log('=> connect to database');
            let connectionString : string = "mongodb://" + MongoConfig.host + ":" + MongoConfig.port;
            return MongoClient.connect(connectionString)
                .then(db => {
                    console.log('=> connected to database');
                    this.cachedDb = db.db(MongoConfig.database);
                    return this.cachedDb;
                });
        }
        public async get() {
            if (this.cachedDb) {
                console.log('=> using cached database instance');
                return Promise.resolve(this.cachedDb);
            }else{
                return this.connectToDatabase();
            }
        }
    }
    let client = new DbClient();
    client.get();
    export = client;

控制台输出在哪里:

=> connect to database
=> connected to database
=> connected to database

这是行为不端的任何特定原因?

【问题讨论】:

  • 旁注:在设置this.cachedDb 之前,可能会多次调用get,这会导致创建Db 的多个连接/实例。这可以通过将connectToDatabase 的承诺分配给私有属性然后在get 中返回该承诺来避免(尽管它可能应该以某种方式处理失败)。
  • 你的 Promise 逻辑有缺陷。除了 David 所建议的之外,get() 方法返回一个包装在一个承诺中的 Promise 或一个已解决的承诺 (this.cachedDb) 包装在一个承诺中。但也不要使用。你从MongoClient.connect() 的承诺返回,但从不使用它(除了从get 返回)。最后,你在 promise 上使用 then 并返回一个永远不会被使用的值。或许您应该回顾一下Mongo Guides 中非常简单的设计
  • @RandyCasburn 在该代码中我没有看到任何包含在承诺中的承诺(返回承诺的承诺)。也许我错过了什么?
  • @David - connectToDatabase() 是异步的并返回一个 Promise 对象,该 Promise 对象由 get() 直接返回,也是一个异步函数,它返回一个包装在 Promise 中的 Promise。
  • @Gleeb - 看看这个gist 这是你的课程以更简洁的方式编写。

标签: javascript typescript mocha.js


【解决方案1】:

共有 2 次调用 get() 函数,1 次在导出之前,如您所见,另一个来自 mocha 测试。

我怀疑输出有一个额外的=> connect to database。正如我在 cmets 中所说:有一个“竞争条件”,在设置 this.cachedDb 之前可以多次调用 get(),这将导致创建 Db 的多个连接/实例。

例如:

const a = client.get();
const b = client.get();

// then
a.then(resultA => {
    b.then(resultB => {
        console.log(resultA !== resultB); // true
    });
});

解决方案

可以通过将承诺存储为缓存值来解决问题(另外,正如 Randy 指出的那样,不需要在方法上使用 async 关键字,因为在任何方法中都没有等待值,因此您可以只需返回承诺):

import {Db, MongoClient} from "mongodb";
import {MongoConfig} from '../config/config'

class DbClient {
    private cachedGet: Promise<Db> | undefined;

    private connectToDatabase() {
        console.log('=> connect to database');
        const connectionString = `mongodb://${MongoConfig.host}:${MongoConfig.port}`;
        return MongoClient.connect(connectionString);
    }

    get() {
        if (!this.cachedGet) {
            this.cachedGet = this.connectToDatabase();

            // clear the cached promise on failure so that if a caller
            // calls this again, it will try to reconnect
            this.cachedGet.catch(() => {
                this.cachedGet = undefined;
            });
        }

        return this.cachedGet;
    }
}

let client = new DbClient();
client.get();
export = client;

注意:我不确定使用 MongoDB 的最佳方式(我从未使用过),但我怀疑连接的寿命不应该长到像这样被缓存(或者应该只缓存短时间然后断开连接)。不过,您需要对此进行调查。

【讨论】:

  • 我使用了你的代码 sn-p 谢谢它实际上非常简洁和正确。关于长期会议,我使用本指南作为我的代码的基础(因为我正在为 Lambda 构建)。 docs.atlas.mongodb.com/best-practices-connecting-to-aws-lambda
  • @Gleeb 好的,很酷。只要是他们推荐的。我想知道您是否需要处理意外断开连接时的重新连接,但也许这一切都在内部处理。我觉得如果您需要,他们会提到这一点。另外,我知道你从哪里得到你的原始代码!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-23
  • 1970-01-01
  • 2012-02-06
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
  • 2017-06-19
相关资源
最近更新 更多