Typescript 中使用异步方法链接(刚刚找到了解决方案)

vmt*_*ran 6 javascript chaining node.js async-await typescript

我正在编写一个模块,旨在在将查询调用到数据库之前准备查询。普通 javascript 中的代码运行得很好,但是当我尝试用 Typescript 编写它时,出现了错误:Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member

我的 JavaScript 代码:

class QueryBuilder {
  constructor(query) {
    this.query = query;
  }

  sort(keyOrList, direction) {
    this.query = this.query.sort(keyOrList);
    return this;
  }

  skip(value) {
    this.query = this.query.skip(value);
    return this;
  }

  limit(value) {
    this.query = this.query.limit(value);
    return this;
  }

  then(cb) {
    cb(this.query.toArray());
  }
}
Run Code Online (Sandbox Code Playgroud)

打字稿中的代码:

class QueryBuilder {
  public query: Cursor;
  constructor(query: Cursor) {
    this.query = query;
  }

  public sort(keyOrList: string | object[] | object, direction: any) {
    this.query = this.query.sort(keyOrList);
    return this;
  }

  public skip(value: number) {
    this.query = this.query.skip(value);
    return this;
  }

  public limit(value: number) {
    this.query = this.query.limit(value);
    return this;
  }

  public then(cb: Function) {
    cb(this.query.toArray());
  }
}
Run Code Online (Sandbox Code Playgroud)

我如何调用这些方法:

const query = await new QueryBuilder(Model.find())
    .limit(5)
    .skip(5)
Run Code Online (Sandbox Code Playgroud)

希望有人能帮助我解决这个问题。提前致谢。

*更新:我从 buitin Promise 扩展了 QueryBuilder 类,然后用 重写了then方法QueryBuilder.prototype.then。代码现在是可执行的,但我并没有真正理解super(executor)构造函数中的内容。这是必需的,executor(resolve: (value?: T | PromiseLike<T> | undefined) => void, reject: (reason?: any) => void): void所以我只是创建了一个愚蠢的执行器。它对代码有何影响?

class QueryBuilder<T> extends Promise<T> {
  public query: Cursor;
  constructor(query: Cursor) {
    super((resolve: any, reject: any) => {
      resolve("ok");
    });
    this.query = query;
  }

  public sort(keyOrList: string | object[] | object, direction?: any) {
    this.query = this.query.sort(keyOrList);
    return this;
  }

  public skip(value: number) {
    this.query = this.query.skip(value);
    return this;
  }

  public limit(value: number) {
    this.query = this.query.limit(value);
    return this;
  }
}

QueryBuilder.prototype.then = function (resolve: any, reject: any) {
  return resolve(this.query.toArray());
};
Run Code Online (Sandbox Code Playgroud)

Win*_*ing 4

问题

\n

TypeScript 在做什么?

\n

用于评估操作数类型的 TypeScript 算法await如下所示(这是一个非常简单的解释)[参考]

\n
    \n
  1. 该类型是承诺吗?如果是,请使用承诺的类型转到步骤 1 。如果否,请转至步骤 2
  2. \n
  3. 该类型是 thenable 吗?如果没有返回类型。如果是,则抛出类型错误,指出““await”操作数的类型必须是有效的 Promise,或者不得包含可调用的“then”成员 (ts1320)”。
  4. \n
\n

TypeScript 在你的例子中做了什么?

\n

现在知道了这一点,我们可以看到 TypeScript 在检查代码时正在做什么。

\n

的操作数await是:

\n
new QueryBuilder(Model.find())\n.limit(5)\n.skip(5)\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 调用skip不会返回承诺。我们进入步骤 2(注意:调用limit或实例化QueryBuilder都没有)。
  2. \n
  3. skipQueryBuilder返回具有可调用成员的实例then。这会导致类型错误:“\'await\' 操作数的类型必须是有效的 Promise,或者不得包含可调用的 \'then\' 成员 (ts1320)”。
  4. \n
\n

您的类定义带有可调用的“then”成员:

\n
class QueryBuilder {\n  public query: Cursor;\n  constructor(query: Cursor) {\n      this.query = query;\n  }\n  \n  ...\n  \n  public then(cb: Function) {\n      cb(this.query.toArray());\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

为什么 TypeScript 会出错?

\n

现在我们了解了 TypeScript 是如何抛出类型错误的。但为什么会抛出这个错误呢?JavaScript 可以让你await任何事情

\n
\n
[rv] = await expression;\n
Run Code Online (Sandbox Code Playgroud)\n

表达式:一个 Promise 或任何要等待的值。
\nrv:返回 Promise 的已实现值,如果不是 Promise,则返回值本身。

\n
\n

\xe2\x80\x93 MDN 文档await

\n

为什么 TypeScript 说“\'await\' 操作数的类型 [如果它不是有效的 Promise] 不得包含可调用的 \'then\' 成员”?为什么它不让你await使用thenable?awaitMDN 甚至给出了一个你可以使用 thenable 的例子。

\n
\n
async function f2() {\n  const thenable = {\n    then: function(resolve, _reject) {\n      resolve(\'resolved!\')\n    }\n  };\n  console.log(await thenable); // resolved!\n}\n\nf2();\n
Run Code Online (Sandbox Code Playgroud)\n
\n

\xe2\x80\x93 MDN 示例awaiting a thenable

\n

TypeScript 的源代码有有用的注释。上面写着:

\n
\n

该类型不是承诺,因此无法进一步解包。\n只要该类型没有可调用的“then”属性,\n返回该类型就是安全的;否则,会报告错误并返回\nundefined。

\n

非承诺“thenable”的一个例子可能是:

\n
await { then(): void {} }\n
Run Code Online (Sandbox Code Playgroud)\n

“thenable”与 Promise 的最小定义不匹配。\n当 Promise/A+ 兼容或 ES6 Promise 尝试采用此值时,\n该 Promise 将永远不会解决。我们将此视为错误,以帮助标记\n运行时问题的早期指示器。如果用户想要从异步函数返回此值,他们需要将其包装在某个值中。如果他们希望将其视为承诺,则可以将其强制转换为<any>

\n
\n

\xe2\x80\x93参考

\n

通过阅读本文,我的理解是 TypeScript 不适await用于非 Promise thenables,因为它不能保证实现符合Promises/A+定义的最低规范,因此假设它是一个错误。

\n

对您的解决方案的评论

\n

在您尝试并添加到问题上下文中的解决方案中,您已定义该类QueryBuilder以扩展本机承诺,然后您已覆盖该then成员。虽然这似乎达到了您想要的效果,但存在一些问题:

\n

你的类实例化有不合理的行为

\n

作为扩展类的结果,您需要先调用其父构造函数,然后才能引用上下文this。父类构造函数的类型是:

\n
(resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void\n
Run Code Online (Sandbox Code Playgroud)\n

正如您所发现的,您需要传递一些满足该合同的内容,除了让它发挥作用之外没有其他原因。此外,在实例化之后,您的类会返回一个用任意值解析的承诺。无关紧要且不合理的代码是混乱、意外行为的根源,并且可能存在错误。

\n

您已经破坏了 Promise 接口定义的类型契约

\n

该接口定义了其他方法,例如catch. 该类的使用者可能会尝试使用该catch方法,尽管合同允许他们这样做,但行为并不符合预期。这引出了下一点。

\n

你没有利用 TypeScript 的优势

\n

您在问题中提到:

\n
\n

普通 JavaScript 中的代码运行得很好,但是当我尝试用 TypeScript 编写它时,出现了错误

\n
\n

类型的存在是有原因的,其中之一是它们通过强制接口之间的契约来减少错误的可能性。如果您尝试解决它们,则会导致混乱、意外行为和错误风险增加。为什么首先要使用 TypeScript?

\n

解决方案

\n

现在我们了解了错误是什么以及发生的原因,我们可以找到解决方案。该解决方案将要求then成员的实现满足 Promises/A+ 中的最低规范,因为我们已确定这是错误的原因。我们只关心接口的规范then,而不是它的实现细节:

\n
    \n
  • 2.2.1onFulfilled都是onRejected可选参数
  • \n
  • 2.2.7 then必须返回一个promise
  • \n
\n

TypeScript 定义then也可供参考(注意:为了便于阅读,我做了一些更改):

\n
/**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\nthen<\n  TResult1 = T,\n  TResult2 = never\n>(\n  onfulfilled?:\n    | ((value: T) => TResult1 | PromiseLike<TResult1>)\n    | undefined\n    | null,\n  onrejected?:\n    | ((reason: any) => TResult2 | PromiseLike<TResult2>)\n    | undefined\n    | null\n): Promise<TResult1 | TResult2>;\n
Run Code Online (Sandbox Code Playgroud)\n

实现本身可能会遵循以下算法:

\n
    \n
  1. 执行您的自定义逻辑
  2. \n
  3. 如果步骤 1 成功解决,则使用自定义逻辑的结果,否则拒绝并显示错误
  4. \n
\n

这是示例实现的演示,可以帮助您开始自己的实现。

\n
class CustomThenable {\n  async foo() {\n    return await \'something\';\n  }\n\n  async then(\n    onFulfilled?: ((value: string) => any | PromiseLike<string>) | undefined | null,\n  ): Promise<string | never> {\n    const foo = await this.foo();\n    if (onFulfilled) { return await onFulfilled(foo) }\n    return foo;\n  }\n}\n\nasync function main() {\n  const foo = await new CustomThenable();\n  console.log(foo);\n  const bar = await new CustomThenable().then((arg) => console.log(arg));\n  console.log(bar);\n}\n\nmain();\n
Run Code Online (Sandbox Code Playgroud)\n

  • 问题是……简而言之,如果我有一个名为“a”的承诺。我调用 `a.then(..); a.then(...);` 按顺序,所以不是链接的.. Promise 的期望是我得到相同的结果,而不是重新执行操作。 (2认同)