Skip to content

Instantly share code, notes, and snippets.

@jason-den
Last active July 29, 2021 07:35
Show Gist options
  • Save jason-den/009b9b980198d88faae262c6dfd061a8 to your computer and use it in GitHub Desktop.
Save jason-den/009b9b980198d88faae262c6dfd061a8 to your computer and use it in GitHub Desktop.
// Reference - https://mongoosejs.com/docs/transactions.html
// "mongoose": "^5.11.2"
const assert = require("assert")
const mongoose = require("mongoose")
const case1 = () => {
// Expect Output:
// >>> case 1 catch error error.message
// >>> case1 finally
const basicThrow = () => {
throw new Error("Thrown from basicThrow()")
}
try {
basicThrow()
} catch (error) {
console.error(">>> case 1 catch error ", error.message)
} finally {
console.log(">>> case1 finally")
}
}
async function case2() {
// Expect Output:
// >>> case2 catch the async error `error.message`
// >>> case2 finally
const asyncThrow = async () => {
throw new Error("Thrown from asyncThrow()")
}
try {
await asyncThrow()
} catch (error) {
console.error(">>> case2 catch the async error", error.message)
} finally {
console.log(">>> case2 finally")
}
}
// Case 3
// 3.1 error-throwing function - performTransaction
const performTransaction = async (executable) => {
const session = await mongoose.startSession()
session.startTransaction()
try {
await executable()
// assert
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
throw error
} finally {
session.endSession()
}
}
// 3.2 model definition and db-connection
const userSchema = new mongoose.Schema({ name: { type: String, required: true } })
const User = mongoose.model("User", userSchema)
module.exports["User"] = User
const goodUserDoc = { name: "jason" }
const badUserDoc = { typoName: "jason" }
const dbConnect = async () => {
try {
// Note that for security concern, this dbUrl is a fake one.
// Please create your own cluster and replace it with your connection URL.
const dbUrl = "mongodb+srv://JasonDen:hello.jason.den@cluster0.jfkuk.mongodb.net/test?retryWrites=true&w=majority"
await mongoose.connect(dbUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
})
} catch (err) {
console.log("DB Connection Error", err)
}
}
// 3.3
const case3_tests = () => {
const sendEndHiringByHirerEmails = async (transactionTag) =>
console.log(`${transactionTag} is successfully performed. So let's start sendEndHiringByHirerEmails`)
const test0 = async () => {
const t0_good = async () => {
console.log("This is t0, no error here.")
}
try {
await performTransaction(t0_good)
await sendEndHiringByHirerEmails("t0_good")
} catch (error) {
console.log('catch the error inside "performTransaction(t0_good)"', error.message)
}
}
const test1 = async () => {
const t1_throw_immediately = async () => {
throw new Error("Thrown from t1_throw_immediately()")
}
try {
await performTransaction(t1_throw_immediately)
await sendEndHiringByHirerEmails("t1_throw_immediately")
} catch (error) {
console.log('catch the error inside "performTransaction(t1_throw_immediately)"', error.message)
}
}
const test2 = async () => {
const t2_throw_by_DB = async () => {
const newUser = await User.create({ name: "jason" })
await newUser.save()
// The following line will throw error. And I want the `newUser.save()` will be aborted
const resultOfErrorOperation = await User.findOneAndUpdate(
{ _id: "An incorrect ID" },
{ email: "hello@world.com", name: "jason" },
)
}
try {
await performTransaction(t2_throw_by_DB)
await sendEndHiringByHirerEmails("t2_throw_by_DB")
} catch (error) {
console.log('>>> test2 catch error inside "performTransaction(t2_throw_by_DB)" ---- \n ', error.message)
} finally {
assert.strictEqual(await User.countDocuments(), 1)
console.log("test2 proves that performTransaction doesn't work. ")
// Base on https://mongoosejs.com/docs/transactions.html I think
// performTransaction is a failed attempt and cannot be fixed.
await User.deleteMany()
assert.strictEqual(await User.countDocuments(), 0)
}
}
const test3 = async () => {
await User.deleteMany()
assert.strictEqual(await User.countDocuments(), 0)
console.log(">>> test3 start; User collection is empty.")
let newUser
let newUser2
const session = await mongoose.startSession()
session.startTransaction()
try {
newUser = await User.create([goodUserDoc], { session }) // NOTE: pass docs as array
newUser2 = await User.create([badUserDoc], { session })
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
return
} finally {
assert.strictEqual(await User.countDocuments(), 0)
console.log(">>> test3 - fanally - transaction failed. ")
console.log(">>> assert.strictEqual(await User.countDocuments(), 0)")
session.endSession()
}
// NOTE: In this block, the failed transaction `return` in catch, so the following line would not be reached
console.log(">>> test3 - after try/catch, here is the user: ", newUser, newUser2)
}
const test4 = async () => {
await User.deleteMany()
assert.strictEqual(await User.countDocuments(), 0)
console.log(">>> test4 - start User collection is empty.")
let newUser
const session = await mongoose.startSession()
session.startTransaction()
try {
newUser = await User.create([goodUserDoc], { session })
await session.commitTransaction()
} catch (error) {
await session.abortTransaction()
return
} finally {
session.endSession()
assert.strictEqual(await User.countDocuments(), 1)
console.log(">>> test4 - fanally - User creation transaction is successful. ")
console.log(">>> assert.strictEqual(await User.countDocuments(), 1)")
}
console.log(">>> test4 - after try/catch, here is the user: ", newUser)
}
return { test0, test1, test2, test3, test4 }
}
const case3 = async () => {
await dbConnect()
const { test0, test1, test2, test3, test4 } = case3_tests()
await test3()
await test4()
}
// case1()
// case2()
case3()
@jason-den
Copy link
Author

Note that for security concern, the dbUrl is a fake one. Please create your own cluster and replace it with your connection URL.
const dbUrl = "mongodb+srv://JasonDen:hello.jason.den@cluster0.jfkuk.mongodb.net/test?retryWrites=true&w=majority"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment