【问题标题】:What return this function in typescript?什么在打字稿中返回这个函数?
【发布时间】:2022-02-11 10:30:29
【问题描述】:

当我将鼠标悬停在关键字“功能”上时,描述会显示:

"(本地函数)(this: any, next: (err?: mongoose.CallbackError | undefined) => void): Promise"

那么它返回一个Promise<void> 还是一个简单的<void>?我什至无法理解这个函数返回什么?老实说,我不太了解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();

})

【问题讨论】:

  • 它是一个异步函数,所以它确实返回了一个承诺。如果你不确定什么是 promise,mdn 是一个很好的资源

标签: node.js typescript mongoose promise return-type


【解决方案1】:

这个问题真的很有趣。你的函数返回一个Promise&lt;void&gt;,它与pre 期望的void 返回类型兼容,但是Mongoose 足够聪明,知道如何处理你的Promise,所以你甚至不必调用@987654333完全是@。


先介绍一下背景:

  • void 在 TypeScript 中有一个 special meaning 表示返回值可以是任何值;该值通常是undefined(因为这是没有return 语句的函数返回的值),但不一定是。 As in the TypeScript FAQ,这使得通过指示返回值未使用来接受或传递返回值的函数变得很方便。如果您需要提供返回类型为 void 的函数,则可以传回返回 stringPromise&lt;void&gt;Promise&lt;SomeObject&gt;nullundefined 或其他任何类型的函数。
  • All async functions return Promises,这也不例外。 Promise&lt;number&gt; 是一个 Promise,表示其 then 函数将收到 numberPromise&lt;void&gt; 是一个 Promise,它不会告诉你它的 then 函数接收到什么。 then 函数仍然会被调用,除非它对catch 有错误;你只是不太了解它的论点。
  • 在 Mongoose 的类型中,pre 采用 PreSaveMiddlewareFunction&lt;T&gt; 函数,这是您编写的函数的类型。它接受一个名为next 的函数并返回void:Mongoose 声称 不在乎你返回什么。你的中间件函数可以是异步的;完成后,您应该调用next(带有错误对象,如果有的话),并且对next 的调用也会返回void

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

所以你的async function 返回Promise&lt;void&gt;,但这适用于pre 的定义:pre 不关心你的函数有什么样的返回值(void),只要你调用@ 987654371@ 表示您已完成。


但是等等!报告您的异步函数已完成以及是否存在错误正是 Promises 旨在解决的问题,而 next 回调模式正是 Promises 旨在解决的那种模式代替。如果你要返回一个 Promise,当 Mongoose 可以看到你返回的 Promise 时,你为什么还要打电话给 next

事实上,在 Mongoose 5.x 或更高版本中,that's exactly what happens 如果您传递给 pre 的函数返回一个 Promise,那么您可以使用它而不是调用 next。为了兼容性,您仍然可以手动调用next,但在您的情况下,您可以删除return next(),一切都会继续工作。 See the middleware docs:

mongoose 5.x 中,您可以使用返回promise 的函数,而不是手动调用next()。特别是,您可以使用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() 完全是一种模式:

如果您使用next()next() 调用不会阻止中间件函数中的其余代码执行。当您调用next() 时,请使用the early return pattern 防止中间件函数的其余部分运行。

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 的预期返回类型与您返回 Promise&lt;void&gt; 的事实兼容,但它隐藏了这样一个事实,即最近版本的 Mongoose 足够聪明,可以检查您是否返回无需致电next 即可承诺并做正确的事。它们是两种不同的风格。

【讨论】:

    【解决方案2】:

    长答案短:它返回一个Promise&lt;void&gt;


    回调

    要了解原因,这里有一些细节。 首先必须了解 node.js 中的回调。回调是 node.js 工作方式的基本结构/功能之一。

    你可以说 node.js 基本上是一个事件驱动的编程“框架”(大多数人会对框架这个词皱眉......)。这意味着您告诉节点,如果发生某件事,它应该执行某个操作/功能(回调)。

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

    在你的情况下,

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

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

    你会注意到你的回调返回next(),但next()返回void,这意味着你的回调返回void。 那么为什么返回Promise&lt;void&gt;

    事实上,在您的情况下,您的回调是一个异步函数。每个异步函数都会返回一个 Promise。它是一个异步函数,因为它正在等待它内部的另一个承诺(甚至两个承诺)。由于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 函数,那么你的程序将是奥运会上最慢的冠军的最爱。

    这些承诺是怎么回事?我们怎么能不被贴上老年项目的标签?


    承诺

    这是一个快速/长评论,试图解释承诺的概念。

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

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

    或者在你的代码中:

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

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

    • 当蛋糕在烤箱里时(承诺),
    • 你准备糖霜,
    • 但您不能在蛋糕烤好之前将其放入(promise 的“then”操作)。

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

    你会在日常生活中有很多“承诺”的例子。您可能听说过异步代码 = 不是一个接一个,不同步,...

    • 早上打开闹钟叫醒你,then 你保证不会忽视它;
    • 在日历上提醒then,您承诺您将参加那次工作面试;等

    在做出这些承诺之后,你一直在继续你的生活。

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

    mypromise().then(doThisThingFunction)
    
    const continueWithMyLife = true
    

    这样then方法就和node.js的回调很像了。它只是在代码中以不同的方式表达,并不特定于节点(回调也不特定于节点......)。 它们之间的一个非常重要的区别是,回调是侦听器“做”的事情,而承诺是(希望)解析为返回值的事情。


    异步/等待

    现在使用async/await 很常见。幸运/不幸的是,它基本上隐藏了异步行为。更好地阅读代码,但对于新程序员来说,对 Promise 的理解也更差。

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

    这就是为什么在您的代码中,hash 值在下一行中可用。基本上是用“旧方式”来写承诺

    user.password = hash;
    

    将在 then 函数内。

    这也是它返回Promise&lt;void&gt;的原因


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

    【讨论】:

      猜你喜欢
      • 2020-02-21
      • 1970-01-01
      • 2022-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多