【问题标题】:Mongoose pass data out of withTransaction helperMongoose 将数据从 withTransaction 助手中传递出去
【发布时间】:2020-06-12 19:31:49
【问题描述】:

简介

你好,

我正在尝试从 mongoose withTransaction 回调传递数据。现在,我正在使用以下实现回调的代码:

const transactionSession = await mongoose.startSession()
await transactionSession.withTransaction(async (tSession) => {
    try {
        // MARK Transaction writes & reads removed for brevity

        console.log("Successfully performed transaction!")
        cb(null, "Any test data")

        return Promise.resolve()
    } catch (error) {
        console.log("Transaction aborted due to error:", error)
        cb(error)

        return Promise.reject()
    }
})

} catch (error) {
    console.log(error)
    return cb(error)
}

可以找到here 使用的withTransaction 助手的更详细的sn-p。

可以在 here 找到有关 withTransaction 助手的官方 Mongoose 文档的链接。

目前,我正在使用回调从withTransactioncallback 传递数据:

cb(null, "Any test data")

但是,问题在于,在返回 Promise.resolve() 之前,自然会先执行回调。这意味着,(在我的情况下)在提交任何必要的数据库写入之前将成功响应发送回客户端:

// this is executed first - the callback will send back a response to the client
cb(null, "Any test data")

// only now, after the response already got sent to the client, the transaction is committed.
return Promise.resolve()

为什么我认为这是个问题:

老实说,我不确定。如果当时没有任何数据库写入,那么将成功响应发送回客户端是不合适的。有人知道处理这个特定用例的适当方法吗?

我考虑过使用类似这样的方法将数据从 withTransaction 助手中传递出来:

const transactionResult = await transactionSession.withTransaction({...})

我试过了,响应是 MongoDB 的 CommandResult,其中不包括我在已解决的承诺中包含的任何数据。

总结

如果在提交事务之前将成功响应发送回客户端,是否有问题?如果是这样,从withTransaction 助手传递数据并因此在发送回响应之前提交事务的适当方法是什么?

如果我得到任何建议,我将不胜感激。

【问题讨论】:

    标签: javascript node.js mongodb mongoose async-await


    【解决方案1】:

    对于如何正确使用 Promises,这里似乎存在一些混淆,在几个层面上。

    Callback 和 Promise 使用不当

    如果函数应该接受回调,则不要返回 Promise。如果该函数应该返回一个 Promise,请使用 Promise 给出的回调:

    const transactionSession = await mongoose.startSession()
    await transactionSession.withTransaction( (tSession) => {
        return new Promise( (resolve, reject) => {
            //using Node-style callback
            doSomethingAsync( (err, testData) => {
                if(err) {
                    reject(err);
                } else {
                    resolve(testData); //this is the equivalent of cb(null, "Any test data")
                }
            });
        })
    

    让我们更详细地看一下:

    return new Promise( (resolve, reject) => { 这会创建一个新的 Promise,Promise 会为您提供两个回调函数供您使用。 resolve 是表示成功的回调。您将要返回的对象传递给它。请注意,我已经删除了 async 关键字(稍后会详细介绍)。

    例如:

    const a = new Promise( (resolve, reject) => resolve(5) );
    a.then( (result) => result == 5 ); //true
    

    (err, testData) => { 该函数用于将 Node 样式的 cb(err, result) 映射到 Promise 的回调。

    try/catch 使用不正确。

    Try/catch 只能用于同步语句。让我们比较一下同步调用、Node 样式(即cb(err, result))异步回调、Promise 和使用 await:

    • 同步:
    try {
        let a = doSomethingSync();
    } catch(err) {
        handle(err);
    }
    
    • 异步:
    doSomethingAsync( (err, result) => {
        if (err) {
            handle(err);
        } else {
            let a = result;
        }
    });
    
    • 承诺:
    doSomethingPromisified()
        .then( (result) => { 
            let a = result; 
        })
        .catch( (err) => {
            handle(err);
        });
    
    • 等待。 Await 可以与任何返回 Promise 的函数一起使用,并让您像处理同步代码一样处理代码:
    try {
        let a = await doSomethingPromisified();
    } catch(err) {
        handle(err);
    }
    

    附加信息

    Promise.resolve()

    Promise.resolve() 创建一个新的 Promise 并使用未定义的值解析该 Promise。这是以下的简写:

    new Promise( (resolve, reject) => resolve(undefined) );
    

    与此等效的回调是:

    cb(err, undefined);
    

    async

    asyncawait 搭配使用。如果您在函数中使用await,则该函数必须声明为async

    就像await 解包一个 Promise(resolve 成一个值,reject 成一个异常),async 包装 代码成一个 Promise。 return value 语句被翻译成Promise.resolve(value),抛出的异常throw e 被翻译成Promise.reject(e)

    考虑下面的代码

    async () => {
        return doSomethingSync();
    }
    

    上面的代码等价于:

    () => {
        const p = new Promise(resolve, reject);
        try {
            const value = doSomethingSync();
            p.resolve(value);
        } catch(e) {
            p.reject(e);
        }
        return p;
    }
    

    如果您在没有await 的情况下调用上述任何一个函数,您将得到一个Promise。如果你await其中任何一个,都会返回一个值,否则会抛出异常。

    【讨论】:

    • 您好,非常感谢您的详细回答!你完全正确,我不是 JS 承诺方面的专家。您能否告诉我以我的方式返回 Promise 和您的方式(使用新的 Promise(resolve,reject))之间的区别是什么?这两种方式都会告诉 withTransaction 助手它是否有效,即是否应该提交数据库交互。另外,在将响应发送回客户端之前,我现在如何能够提交数据库写入?您能否提供一些示例代码?非常感谢:)
    • @linus_hologram 当然,给我一点时间,我会编辑答案
    • @linus_hologram 我添加了一些关于Promise.resolve() 的信息。但我意识到,如果你给我更多关于你编辑的部分的详细信息,对你的帮助会更容易
    • 嘿,当然!我会链接一个要点,给我几分钟。
    • 嘿,请看更新的答案。您可以在第一个代码 sn-p 下方找到指向要点的链接 :)
    猜你喜欢
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-28
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多