return 这个函数在 typescript 中是什么?

What return this function in typescript?

当我将鼠标悬停在关键字 'function' 上时,描述显示:

"(local function)(this: any, next: (err?: mongoose.CallbackError | undefined) => void): Promise<void>"

它 return 是 Promise<void> 还是简单的 <void>?我什至不明白这个功能 returns 是什么?老实说,我不太了解 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();

})

这个问题真有意思。您的函数 return 是 Promise<void>,它与 pre 期望的 void return 类型兼容,但 Mongoose 非常聪明,知道该做什么使用您的 Promise,您甚至根本不需要调用 next


首先是一些背景:

  • void有一个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,这使得通过指示return值未使用来接受或传递return值的函数很方便。如果您需要提供 return 类型 void 的函数,您可以传回 return 为 stringPromise<void>Promise<SomeObject> 的函数、nullundefined 或其他任何内容。
  • All async functions return Promises,这次也不例外。 Promise<number> 是一个 Promise,表示其 then 函数将接收 numberPromise<void> 是一个 Promise,它不会告诉您任何有关其 then 函数接收的信息。 then 函数仍然会被调用,除非它对 catch 有错误;你只是不太了解它的论点。
  • 在Mongoose的types中,pre takes a PreSaveMiddlewareFunction<T>function,就是你写的函数的类型。它接受一个名为 next 和 returns void 的函数:猫鼬 声称 不关心你 return。你的中间件函数允许是异步的;完成后,您需要调用 next(如果有错误对象,则使用错误对象),并且对 next 的调用也会 returns void

你的函数传递给 pre return 类型 Promise<void>:函数是 async 所以它绝对 return 是一个承诺,你的 return next(); 意味着 Promise 解析为任何 next returns,它被定义为 void。你不知道 next return 是什么,不应该关心它。您甚至不需要 return next(),您只需要调用它:它只是一个回调,因此您可以告诉 Mongoose 您的中间件已完成并报告任何错误。

所以您的 async function returns Promise<void>,但这适用于 pre 的定义:pre 不关心哪种 return 只要您调用 next 表示您已完成,您的函数就具有 (void) 的值。


但是等等!报告你的异步函数完成了,有没有错误,正是Promises设计来解决的问题,next回调模式正是这种模式Promises 旨在取代。如果您正在 return 一个 Promise,当 Mongoose 只能监视您 return 的承诺时,您为什么还需要调用 next

事实上,在 Mongoose 5.x 或更高版本中,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 use async/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();
});

文档进一步解释了为什么 return next() 是一个模式:

If you use next(), the next() call does not stop the rest of the code in your middleware function from executing. Use the early return pattern to prevent the rest of your middleware function from running when you call next().

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');
});

总而言之,void 的预期 return 类型与您正在 returning Promise<void> 这一事实兼容,但它隐藏了这样一个事实最新版本的 Mongoose 足够智能,无需调用 next 即可检查您是否正在 return 进行 Promise 并做正确的事情。它们是两种不同的风格,但都有效。

长答短:它return一个Promise<void>


回调

为了理解原因,这里有一些细节。 第一个必须了解 node.js 中的回调。回调是 node.js 工作方式的基本 structure/feature 之一。

您可以说 node.js 基本上是一个 Event-Driven 编程“框架”(大多数人会对框架一词皱眉...)。这意味着你告诉节点,如果某件事情发生了,它应该做某件事action/function(回调)。

为了节点理解我们,我们通常将回调函数作为参数传递给另一个函数,该函数将完成“监听事件”并执行我们提供的回调的工作。所以执行回调的不是“我们”,而是事件监听器。

在你的情况下,

userSchema.pre('save', async function (next) {

pre是function(Mongoose的userSchema中的一个方法),save是必须响应的事件,async function (next) {是回调或者在事件之后必须做的事情事件。

您会注意到您的回调是 returning next(),但是 next() returns void,这意味着您的回调是 returning void。 那么为什么 returning Promise<void>?

事实上,在您的情况下,您的回调是一个异步函数。每个异步函数都会 return 一个承诺。它是一个异步函数,因为它正在等待其中的另一个承诺(甚至两个承诺)。它们被隐藏是因为 await

const salt = await bcrypt.genSalt(config.get<number>('saltWorkFactor'));
const hash = await bcrypt.hash(user.password, salt);

注意:bcrypt 方法在 CPU 和时间方面非常昂贵(除其他外,这也是一项安全功能)。

这也意味着通常在您的代码中

const hash = await bcrypt.hash(user.password, salt);
user.password = hash;

您无法“立即”获得 user.passwordhash 值,更糟糕的是,您甚至不知道它什么时候到来。您的程序会停止并等待 bcrypt 完成其业务吗? 如果你有很多 async 功能,你的程序将成为奥运会最慢冠军的最爱。

这些承诺是怎么回事,我们怎么能不被标记为老年项目?


承诺

这里有一条 quick/long 评论试图解释承诺的概念。

在“正常”代码中,每一行代码都在下一行之前执行并“完成”。例如:(烹饪)

  • 混合黄油和糖,
  • 一次加一个鸡蛋,等等

或者在您的代码中:

 let user = this as UserDocument;
if(!user.isModified('password')){
    return next();
}

promise 是在下一行代码之前执行但未完成的特定代码。例如:

  • 当蛋糕在烤箱里时(保证),
  • 你准备糖霜,
  • 但是你不能把它放在蛋糕烤好之前(承诺的“然后”动作)。

注意:您的代码使用的是 await,因此没有“显式”then 方法。

你在日常生活中会有很多“承诺”的例子。您可能听说过异步代码 = not one after other, not in sync, ...

  • 早上闹钟叫醒你,then你保证不会忽视它;
  • 在日历上做个提醒 then 你承诺会去面试;等等

一直以来,您在做出这些承诺后继续生活。

在代码中,return 承诺的函数将有一个 then 方法,您可以在该方法中告诉计算机在“警报响起”时该做什么。 一般是这样写的

mypromise().then(doThisThingFunction)

const continueWithMyLife = true

在这种情况下,then 方法与 node.js 的回调非常相似。它只是在代码中以不同的方式表达而不是特定于节点(回调也不是特定于节点......)。 它们之间的一个非常重要的区别是回调是听众“做”的事情,而承诺是解决(希望)到 returning 值的事情。


Async/Await

现在常用async/await。 Fortunately/unfortunately 它基本上隐藏了异步行为。更好地阅读代码,但对新程序员的承诺理解更差。

await之后,没有then方法(或者你可以说下面这行代码是then动作)。没有“继续你的生活”。只有“等到警报响起”,所以 await 之后的下一行本质上是“起床动作”。

这就是为什么在您的代码中,hash 值在下一行可用。基本上是用“老办法”来写 promise

user.password = hash;

将在 then 函数内。

这也是 returning Promise<void>

的原因

但是,所有这些类比都无济于事。最好是在日常代码中尝试一下。没有什么比经历更能理解任何事了。