如何知道一个函数是否异步?


77

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

async function() {
 // Some async actions
}

因此,我想执行await callback()callback()取决于它所接收的函数的类型。

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


7
不要试图检测它,并根据获得的结果执行不同的操作。清楚地记录您是否支持返回诺言的回调,然后将其视为此类。(提示:如果您await不承诺,它将自动将其包装)
Bergi

3
异步的全部要点是没有回调,对吗?
费利佩·瓦尔德斯

Answers:


65

理论

转换为字符串时,本async函数可能是可识别的:

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

或按AsyncFunction构造函数:

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

asyncFn instanceof AsyncFunction === true

这不适用于Babel / TypeScript输出,因为asyncFn这是已编译代码中的常规函数​​,它是Function或的实例GeneratorFunction,不是AsyncFunction。为了确保不会对编译后的代码中的生成器和常规函数产生误报

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

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

由于本机async函数于2017年正式引入Node.js,因此问题可能涉及Babelasync函数的实现,该实现依赖于transform-async-to-generator转换async为生成器函数,也可能用于transform-regenerator将生成器转换为常规函数。

async函数调用的结果是一个承诺。根据该建议,可以将承诺或不承诺传递给await,这await callback()是普遍的。

只有少数情况可能需要这样做。例如,本机async函数在内部使用本机承诺,并且Promise如果其实现发生更改,则不会在全局范围内使用:

let NativePromise = Promise;
Promise = CustomPromiseImplementation;

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

这可能会影响函数的行为(这是Angular和Zone.js promise实现的一个已知问题)。即使那样,最好还是检测到函数返回值不是预期的Promise实例,而不是检测函数是否为async,因为相同的问题适用于使用替代promise实现的任何函数,而不仅仅是async解决上述Angular问题的方法是包装asyncreturn值Promise.resolve)。

实践

从外部看,async函数只是一个无条件返回本机承诺的函数,因此应将其视为一个。即使一次定义了功能async,也可以在某个时候对其进行转换并成为常规函数。

可以返回承诺的函数

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

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

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

在ES2017中,这是通过以下方式完成的await(这是应该编写问题中的示例的方式):

let result = await fnThatPossiblyReturnsAPromiseOrThrows();
...

一个应返回承诺的函数

检查对象是否是一个承诺是一个单独的问题,但是通常为了覆盖极端情况,它不应过于严格或过于宽松。instanceof Promise如果Promise替换了global可能无法正常工作, Promise !== (async () => {})().constructor。当Angular和非Angular应用程序接口时,可能会发生这种情况。

首先需要调用必须为be的函数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。您能否看一下这个问题并提供您的见解。我一直在尝试许多不同的方法,但是还没有成功。:(
Tums

@Tums那是因为AsyncFunction !== Function检查可以避免误报。在转译的代码中不会有真正的肯定,因为async函数与转译的代码中的常规函数​​没有区别。
Estus Flask

我正在写一个钩子函数,该函数需要一个对象,目标和钩子...我怎么知道我是否必须等待?
Erik Aronesty

@ErikAronesty您能提供一个例子吗?如果值可以是一个承诺,也可以不是一个承诺,则需要await,它适用于承诺和非承诺。这就是答案中的最后一个片段。
Estus Flask,

@EstusFlask: stackoverflow.com/questions/10273309/… 看看我如何不能只是“等待”……因为那样的话,我将改变挂钩函数的语义。
Erik Aronesty

26

我喜欢这种简单的方法:

theFunc.constructor.name == 'AsyncFunction'

1
比起字符串化,这还具有更出色的表现:)
538ROMEO'Feb

鸭子输入的问题是自定义函数通过了此检查theFunc = new class AsyncFunction extends Function {}。但transpiledasync功能没有,theFunc = () => __awaiter(void 0, void 0, void 0, function* () { })
Estus Flask

1
当然,@ EstusFlask,您完全正确。如果是您的情况,则需要一个更复杂的解决方案。但是在“现实世界”中(不是超级特殊情况或人为情况),人们可以使用此解决方案,而不是过度使用怪物检查器。但是,您应该知道您在说什么,谢谢您的评论!
亚历山大

为什么不按=== 'AsyncFunction'@theVoogie的建议使用?
themefield

@Alexander,在现实世界中,非异步函数始终像异步函数一样返回诺言。
cababunga

24

@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问题。如前所述,异步函数仅返回promise,因此根本不应检测它们。顺便说一句,它们无法在缩小的编译代码中可靠地检测到,_ref不会存在。
Estus Flask,

1
除此之外,还有一个小问题,很多时候人们会将节点样式的回调包装到诺言包装中,以与异步函数一起使用,因此该函数可能在一定程度上是异步的,但实际上不是异步的。无论哪种情况,await都可以工作...可能会变得很复杂的是异步生成器。
Tracker1

1
但是,这仍然是一个有效的问题。在仅使用异步和等待的代码中,知道一个函数是否被声明为异步是很重要的,与异步/等待如何在后台实现无关紧要。例如,如果您的API包装器需要确保处理程序被声明为异步,以便它可以引发用户可以修复的错误,那么您需要一个原始问题的答案,而这个问题就可以了。因此,要添加到此答案:另一种本地检查的方法是fn.constructor.name,这将AsyncFunction用于异步功能。
Mike'Pomax'Kamermans

@ Mike'Pomax'Kamermans这个问题是由于对await语义的不正确理解所致。函数是否async处于我所知道的任何实际场景中都没有关系。async只是一个无条件返回本机Promise的函数-应该被视为一个函数。async可能在某个时候被转译,这不应该破坏应用程序。对于您所描述的场景,包装器调用一个函数并断言一个值是promise是正确的,而不是断言一个函数为is是正确的async。如果需要,以防止无效处理程序尽快,这已经在设计时使用TS /流被强制执行
Estus瓶

请记住,仅仅因为您不了解任何实际情况,并不意味着就没有。因此,这是一个需要学习的新知识:您可以发现一个函数是否异步,这意味着您可以编写将对异步函数“执行”操作或对异步函数“执行操作”的代码,而不保留常规函数(反之亦然)。对普通代码有用吗?不,我也无法想到您需要的方案。但是,这对于自己用JS编写的代码分析,AST构建器或编译器是否重要?是的,实际上很重要。
Mike'Pomax'Kamermans

11

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

使用本机util函数

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

不过,请不要忘记回答所有这些问题。偶然偶然返回一个承诺的函数将返回假负数。

最重要的是(来自文档):

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

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


4

TL; DR

简短答案:暴露instaceof后使用 AsyncFunction-见下文。

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

怎么做

您可以检测是否使用 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以了解该函数是否在内部执行了异步/等待操作,但未返回任何内容。
阿米特·库玛·古普塔

1
@AmitGupta它什么也不返回。它返回一个承诺。
Estus Flask,

如果你有一个代码库混合异步/的await(这需要知道任何关于承诺),并保证功能,真正多数民众赞成你不应该做的事情。异步/等待的好处是实现细节变得无关紧要:您不是then().catch()异步函数,try/await而是异步函数。所以是的,你完全应该检查函数的类型,如果你leitimately需要知道它是否是异步或没有,但不能使用instanceof:使用fn.constructor.name代替。如果AsyncFunction不是Function,则说明它是一个异步函数。
Mike'Pomax'Kamermans

4

似乎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()

2

这是David Walsh在他的博客文章中提供的一种方法:

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

干杯!


0

您可以一开始就假设回调是应许的:

export async function runSyncOrAsync(callback: Function) {

  let promisOrValue = callback()
  if (promisOrValue instanceof Promise) {
    promisOrValue = Promise.resolve(promisOrValue)
  }
  return promisOrValue;
}

他们在您的代码中,您可以执行以下操作:

await runSyncOrAsync(callback)

这将在不知道回调类型的情况下解决您的问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.