When I hover on the keyword 'function' the description says:
"(local function)(this: any, next: (err?: mongoose.CallbackError | undefined) => void): Promise<void>"
So does It return a Promise<void>
or a simple <void>
? I can't even understand what does this function returns? And to be honest I don't understand really well the concept of Promise<void>
...
userSchema.pre('save', async function (next) {
let user = this as UserDocument;
if(!user.isModified('password')){
return next();
}
const salt = await bcrypt.genSalt(config.get<number>('saltWorkFactor'));
const hash = await bcrypt.hash(user.password, salt);
user.password = hash;
return next();
})
This question is really interesting. Your function returns a Promise<void>
, which is compatible with the void
return type that pre
is expecting, but Mongoose is quietly smart enough to know what to do with your Promise so you don't even have to call next
at all.
First some background:
void
has a special meaning in TypeScript to mean that the return value could be any value; the value is frequently undefined
(because that's what a function returns without a return
statement) but it doesn't have to be. As in the TypeScript FAQ, this makes it convenient to accept or pass functions that return a value by indicating the return value is unused. If you need to supply a function with return type void
, you could pass back a function that returns a string
, Promise<void>
, Promise<SomeObject>
, null
, undefined
, or anything else.async
functions return Promises, and this is no exception. A Promise<number>
is a Promise that says that its then
function will receive a number
; a Promise<void>
is a Promise that doesn't tell you anything about what its then
function receives. The then
function will still be called, unless it has an error to catch
; you just don't know much about its argument.pre
takes a PreSaveMiddlewareFunction<T>
function, which is the type of the function you wrote. It accepts a function called next
and returns void
: Mongoose claims not to care what you return. Your middleware function is allowed to be asynchronous; when you're done you're expected to call next
(with an error object, if you have one), and that call to next
also returns void
.Your function passed to pre
returns type Promise<void>
: The function is async
so it absolutely returns a promise, and your return next();
means that the Promise resolves to whatever next
returns, which is defined as void
. You don't know what next
returns and shouldn't care about it. You don't even need to return next()
, you just need to call it: It's just a callback so you can tell Mongoose your middleware is done and report any errors.
So your async function
returns Promise<void>
, but that works with the definition of pre
: pre
doesn't care what kind of return value your function has (void
) as long as you call next
to indicate you're done.
But wait! Reporting that your asynchronous function is done and whether or not there were errors is exactly the problem that Promises were designed to solve, and the next
callback pattern is exactly the kind of pattern that Promises were designed to replace. If you're returning a Promise, why would you need to call next
at all when Mongoose can just watch the promise you return?
In fact, in Mongoose 5.x or later, that's exactly what happens: If the function you pass into pre
returns a Promise, then you can use that instead of calling next
. You can still call next
manually for compatibility's sake, but in your case you could delete return next()
and everything would keep working. See the middleware docs:
In mongoose 5.x, instead of calling
next()
manually, you can use a function that returns a promise. In particular, you can useasync/await
.schema.pre('save', function() { return doStuff(). then(() => doMoreStuff()); }); // Or, in Node.js >= 7.6.0: schema.pre('save', async function() { await doStuff(); await doMoreStuff(); });
The docs further explain why return next()
is a pattern at all:
If you use
next()
, thenext()
call does not stop the rest of the code in your middleware function from executing. Use the earlyreturn
pattern to prevent the rest of your middleware function from running when you callnext()
.const schema = new Schema(..); schema.pre('save', function(next) { if (foo()) { console.log('calling next!'); // `return next();` will make sure the rest of this function doesn't run /*return*/ next(); } // Unless you comment out the `return` above, 'after next' will print console.log('after next'); });
In summary, the expected return type of void
is compatible with the fact that you're returning a Promise<void>
, but it hides the fact that recent versions of Mongoose are smart enough to check whether you're returning a Promise and do the right thing without needing a call to next
. They're two different styles that both work.