【问题标题】:How to know if a function is async?如何知道函数是否异步?
【发布时间】:2016-11-25 07:02:30
【问题描述】:

我必须将一个函数传递给另一个函数,并将其作为回调执行。问题是有时这个函数是异步的,比如:

async function() {
 // Some async actions
}

所以我想根据接收到的函数类型执行await callback()callback()

有没有办法知道函数的类型??

【问题讨论】:

  • 不要试图检测它并根据你得到的做不同的事情。清楚地记录您是否支持返回承诺的回调,然后这样对待它们。 (提示:如果你 await 不承诺,无论如何它都会自动包装它)
  • 异步的全部意义在于没有回调,对吧?

标签: javascript node.js async-await ecmascript-next


【解决方案1】:

理论

本机async 函数可能是可识别的when being converted to strings

asyncFn[Symbol.toStringTag] === 'AsyncFunction'

或者通过AsyncFunction构造函数:

const AsyncFunction = (async () => {}).constructor;

asyncFn instanceof AsyncFunction === true

这不适用于 Babel/TypeScript 输出,因为 asyncFn 是转译代码中的常规函数​​,它是 FunctionGeneratorFunction 的实例,而不是 AsyncFunction。为了确保它不会在转译代码中为生成器和常规函数提供误报

const AsyncFunction = (async () => {}).constructor;
const GeneratorFunction = (function* () => {}).constructor;

(asyncFn instanceof AsyncFunction && AsyncFunction !== Function && AsyncFunction !== GeneratorFunction) === true

由于原生 async 函数是在 2017 年正式引入 Node.js 的,所以这个问题可能是指 async 函数的 Babel 实现,它依赖于 transform-async-to-generatorasync 转换为生成器函数,也可以使用transform-regenerator 将生成器转换为常规函数。

async 函数调用的结果是一个承诺。 According to the proposal,promise 或 non-promise 可以传递给await,所以await callback() 是通用的。

只有少数情况下可能需要这样做。例如,原生 async 函数在内部使用原生 Promise,如果其实现发生更改,则不会选择全局 Promise

let NativePromise = Promise;
Promise = CustomPromiseImplementation;

Promise.resolve() instanceof Promise === true
(async () => {})() instanceof Promise === false;
(async () => {})() instanceof NativePromise === true;

这可能会影响函数行为(这是Angular and Zone.js promise implementation 的一个已知问题)。即使这样,最好检测函数返回值不是预期的Promise 实例而不是检测函数是async,因为同样的问题适用于任何使用替代承诺实现的函数,而不仅仅是async (@ 987654327@ 是将async 返回值与Promise.resolve 包装在一起。

练习

从表面上看,async 函数只是一个无条件返回原生 promise 的函数,因此它应该被视为一个函数。即使一个函数曾经被定义为async,它也可以在某个时候被转译并成为常规函数。

一个可以返回承诺的函数

在 ES6 中,可能返回 promise 的函数可以与 Promise.resolve(允许同步错误)或包装 Promise 构造函数(处理同步错误)一起使用:

Promise.resolve(fnThatPossiblyReturnsAPromise())
.then(result => ...);

new Promise(resolve => resolve(fnThatPossiblyReturnsAPromiseOrThrows()))
.then(result => ...);

在 ES2017 中,这是通过 await 完成的(问题中的示例应该是这样写的):

let result = await fnThatPossiblyReturnsAPromiseOrThrows();
...

应该返回一个承诺的函数

检查一个对象是否是一个承诺是a matter of a separate question,但通常它不应该太严格或太松以涵盖极端情况。如果全局 Promise 被替换 Promise !== (async () => {})().constructorinstanceof Promise 可能无法工作。当 Angular 和非 Angular 应用程序接口时,可能会发生这种情况。

一个需要async的函数,即总是返回一个promise应该首先被调用,然后返回值被检查为一个promise:

let promise = fnThatShouldReturnAPromise();
if (promise && typeof promise.then === 'function' && promise[Symbol.toStringTag] === 'Promise') {
  // is compliant native promise implementation
} else {
  throw new Error('async function expected');
}

TL;DR:async 函数不应与返回承诺的常规函数​​区分开来。没有可靠的方法也没有实际的理由来检测非本地转译的async 函数。

【讨论】:

  • 这对我不起作用。 AsyncFunction !== Function 总是错误的,即使我有带有关键字 async 的函数作为参数传递给 it() 规范。顺便说一句,我正在使用 Typescript。请您看看这个question 并提供您的见解。我一直在尝试很多不同的方法,但还没有成功。 :(
  • @Tums 那是因为AsyncFunction !== Function 检查是为了避免误报。转译代码中不会有真阳性,因为async 函数与转译代码中的常规函数​​没有区别。
  • 我正在编写一个钩子函数,该函数接受一个对象、目标和钩子......我怎么知道我是否必须等待?
  • @ErikAronesty 你能提供一个单行的例子吗?如果一个值可以是一个承诺或不是一个承诺,你需要await,它适用于承诺和非承诺。这就是答案中最后一个 sn-p 所显示的内容。
  • @EstusFlask: stackoverflow.com/questions/10273309/… 看看我怎么不能只是“等待”......因为那样我就会改变挂钩函数的语义。
【解决方案2】:

我更喜欢这种简单的方式:

theFunc.constructor.name == 'AsyncFunction'

【讨论】:

  • 这还具有比 stringify 性能更高的优势 :)
  • 鸭子类型的问题是自定义函数通过了这个检查,theFunc = new class AsyncFunction extends Function {}。但是转译的async 函数没有,theFunc = () => __awaiter(void 0, void 0, void 0, function* () { })
  • 当然@EstusFlask,你是完全正确的。如果是您的情况 - 您需要更复杂的解决方案。但是在“现实世界”中(不是超级特殊或人为的情况) - 人们可以使用这种解决方案,而不是过度杀伤怪物跳棋。但是应该知道你在说什么,谢谢你的评论!
  • 为什么不像@theVoogie 建议的那样使用=== 'AsyncFunction'
  • @Alexander,在现实世界中,非异步函数总是返回承诺,就像异步函数一样。
【解决方案3】:

@rnd 和@estus 都是正确的。

但是要在这里用一个实际可行的解决方案来回答这个问题

function isAsync (func) {
    const string = func.toString().trim();

    return !!(
        // native
        string.match(/^async /) ||
        // babel (this may change, but hey...)
        string.match(/return _ref[^\.]*\.apply/)
        // insert your other dirty transpiler check

        // there are other more complex situations that maybe require you to check the return line for a *promise*
    );
}

这是一个非常有效的问题,我很不高兴有人投了他的票。这种检查的主要用例是库/框架/装饰器。

现在还处于早期阶段,我们不应该对有效问题投反对票。

【讨论】:

  • 我猜这个问题的问题是它是XY问题。正如已经提到的,异步函数只返回承诺,因此根本不应该检测到它们。顺便说一句,它们无法在缩小的转译代码中被可靠地检测到,_ref 不会存在。
  • 除此之外的一个小问题是,很多时候人们会将节点样式的回调包装到 Promise 包装器中以与异步函数一起使用,因此该函数可能在合理程度上是异步的,但不是真正的异步. await 可能在任何一种情况下都有效......它可能变得复杂的是异步生成器。
  • 不过,这仍然是一个有效的问题。在只使用 async 和 await 的代码中,知道函数是否被声明为 async 很重要,与 async/await 如何在后台实现无关。例如,如果您的 API 包装器需要确保将处理程序声明为异步,以便它可以抛出用户可以修复的错误,那么您需要原始问题的答案,而这个问题就可以了。所以要添加到这个答案中:另一种本地检查方法是fn.constructor.name,对于异步函数,它将是AsyncFunction
  • @Mike'Pomax'Kamermans 这个问题是由于对await语义的错误理解造成的。在我知道的任何实际场景中,函数是否为 async 都没有关系。 async 只是一个无条件返回本机承诺的函数 - 应该被视为一个函数。 async 可以在某个时候被转译,这不应该毁掉一个应用程序。对于您描述的场景,包装器调用函数并断言要承诺的值是正确的,而不是将函数断言为async。如果需要尽快阻止无效处理程序,则必须在设计时使用 TS/Flow 强制执行
  • 请记住,仅仅因为您不了解任何实际情况,这并不意味着there are none。所以这是要学习的新东西:你可以发现一个函数是否是异步的,这意味着你可以编写代码来“做”异步函数或异步函数,同时不理会常规函数(反之亦然)。它对普通代码有用吗?不,我也想不出你需要它的场景。但这对于本身用 JS 编写的代码分析、AST 构建器或转译器来说重要吗?是的:实际上非常重要。
【解决方案4】:

如果您使用的是 NodeJS 10.x 或更高版本

使用native util function

   util.types.isAsyncFunction(function foo() {});  // Returns false
   util.types.isAsyncFunction(async function foo() {});  // Returns true

请记住以上分析者的所有担忧。一个偶然返回一个承诺的函数,将返回一个假阴性。

除此之外(来自文档):

请注意,这只会报告 JavaScript 引擎看到的内容;特别是,如果使用了转译工具,返回值可能与原始源代码不匹配。

但是如果你在 NodeJS 10 中使用 async 并且没有转换。这是一个很好的解决方案。

【讨论】:

    【解决方案5】:

    看来await 也可以用于正常功能。我不确定它是否可以被认为是“好的做法”,但它是:

    async function asyncFn() {
      // await for some async stuff
      return 'hello from asyncFn' 
    }
    
    function syncFn() {
      return 'hello from syncFn'
    }
    
    async function run() {
      console.log(await asyncFn()) // 'hello from asyncFn'
      console.log(await syncFn()) // 'hello from syncFn'
    }
    
    run()
    

    【讨论】:

      【解决方案6】:

      这是 David Walsh 在其blogpost 中提供的一种简短而有用的方法:

      const isAsync = myFunction.constructor.name === "AsyncFunction";
      

      干杯!

      【讨论】:

        【解决方案7】:

        TL;DR

        简答:在exposing AsyncFunction 之后使用instaceof - 见下文。

        长答案:不要那样做 - 见下文。

        怎么做

        您可以检测函数是否使用async 关键字声明

        当你创建一个函数时,它表明它是一个函数类型:

        > f1 = function () {};
        [Function: f1]
        

        您可以使用instanceof 运算符对其进行测试:

        > f1 instanceof Function
        true
        

        当你创建一个异步函数时,它表明它是一个 AsyncFunction 类型:

        > f2 = async function () {}
        [AsyncFunction: f2]
        

        所以人们可能期望它也可以使用instanceof 进行测试:

        > f2 instanceof AsyncFunction
        ReferenceError: AsyncFunction is not defined
        

        这是为什么呢?因为 AsyncFunction 不是全局对象。请参阅文档:

        尽管如您所见,它列在Reference/Global_Objects...下...

        如果您需要轻松访问AsyncFunction,那么您可以使用我的unexposed 模块:

        获取局部变量:

        const { AsyncFunction } = require('unexposed');
        

        或者在其他全局对象旁边添加一个全局 AsyncFunction

        require('unexposed').addGlobals();
        

        现在上述工作按预期工作:

        > f2 = async function () {}
        [AsyncFunction: f2]
        > f2 instanceof AsyncFunction
        true
        

        为什么你不应该这样做

        上面的代码将测试函数是否是用async关键字创建的,但请记住,真正重要的不是函数是如何创建的,而是函数是否返回一个promise。

        您可以在任何地方使用此“异步”功能:

        const f1 = async () => {
          // ...
        };
        

        你也可以使用这个:

        const f2 = () => new Promise((resolve, reject) => {
        });
        

        即使它不是使用async 关键字创建的,因此不会与instanceof其他答案中发布的任何其他方法匹配

        具体来说,考虑一下:

        const f1 = async (x) => {
          // ...
        };
        
        const f2 = () => f1(123);
        

        f2 只是带​​有硬编码参数的f1,在此处添加async 没有多大意义,即使结果在各方面都与f1 一样“异步”。

        总结

        因此,可以检查是否使用 async 关键字创建了一个函数,但请谨慎使用它,因为当您检查它时,很可能您做错了什么。

        【讨论】:

        • 我能理解“为什么你不应该这样做”,检查一个函数是否用async 声明以了解它是否在内部执行一些异步/等待操作但返回是很好的什么都没有。
        • @AmitGupta 它什么也不返回。它返回一个承诺。
        • 如果您的代码库混合了 async/await(这需要对 Promise 一无所知)和 Promise 函数,那么真的那是你不应该做的事情。 async/await 的好处是实现细节变得无关紧要:你不是 then().catch() 一个异步函数,而是你 try/await 它。所以,是的,如果你真的需要知道它是否是异步的,你完全应该检查函数的类型,而不是使用instanceof:改用fn.constructor.name。如果是AsyncFunction 而不是Function,你就知道这是一个异步函数。
        【解决方案8】:

        您可以在一开始就假设回调是承诺:

        export async function runSyncOrAsync(callback: Function) {
        
          let promisOrValue = callback()
          if (promisOrValue instanceof Promise) {
            promisOrValue = Promise.resolve(promisOrValue)
          }
          return promisOrValue;
        }
        

        在你的代码中你可以这样做:

        await runSyncOrAsync(callback)
        

        这将解决您不知道回调类型的问题....

        【讨论】:

          【解决方案9】:

          完整解决方案:同时处理 Async 和 Promise

          我总是交替使用 Promises 和 async/await,因为它们基本相同。

          Async/Await 用于处理异步函数中的 Promise。它基本上是 promise 的语法糖。它只是重新设计代码并使 Promise 更易于阅读和使用的包装器。 来源:GeeksForGeeks

          如果您需要一个辅助函数来确定一个值是asynchronous function 还是返回一个Promise,我建议创建一个函数来检查该值是否是一个返回 Promise 实例的函数,因为它们都可以.

          在本例中,我将介绍三种不同的方法。

          1. 检查函数是否为异步/等待函数。
          2. 检查常规函数是否返回 Promise。
          3. 检查两者。

          处理异步函数

          这个函数可以判断一个函数是否是使用async关键字定义的。

          示例

          async function a() {}
          const b = async () => {}
          

          解决方案

          function isAsyncFunction(f: unknown): boolean {
            return f && f.constructor.name === 'AsyncFunction'
          }
          

          处理 Promise 函数

          此函数可以确定常规函数是否返回Promise。为了评估给定函数是否返回 Promise,我们需要调用该函数并检查返回值。为了避免多次调用同一个函数,如果是 Promise,我们可以返回上述值,如果不是,则返回 false

          示例

          function a() { return new Promise(() => {}) }
          const b = () => new Promise(() => {})
          

          解决方案

          function isPromiseFunction<T>(fn: any, ...params: any[]): Promise<T> | boolean {
            const isFunction = fn && typeof fn === 'function'
            const notAsyncFunction = fn.constructor.name !== 'AsyncFunction'
              if (isFunction && notAsyncFunction) {
                  const value = fn(...params) || false
                  if (value && value.constructor.name === 'Promise') {
                      return value as Promise<T>
                  }
              }
              return false
          }
          

          同时处理

          因为AsyncFunctionPromise 本质上是相同的,所以我们可以检查它们是否都返回一个Promise。

          function isAsync<T>(fn: any, ...params: any[]): Promise<T> | boolean {
              const isFunction = fn && typeof fn === 'function'
              if (isFunction) {
                  const value = fn(...params) || false
                  if (value && value.constructor.name === 'Promise') {
                      return value as Promise<T>
                  }
              }
              return false
          }
          

          结论

          异步函数验证起来更快、更简洁,而 promise 函数需要调用才能进行验证。

          测试函数(CodePen)

          https://codepen.io/isakhauge/pen/gOGGQPY

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-06-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-29
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多