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

IT技术 javascript node.js async-await ecmascript-next
2021-02-28 11:50:09

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

async function() {
 // Some async actions
}

所以我想执行await callback()callback()取决于它接收的功能类型。

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

6个回答

理论

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

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

或者通过AsyncFunction构造函数:

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

asyncFn instanceof AsyncFunction === true

这不适用于 Babel/TypeScript 输出,因为asyncFn它是转译代码中的常规函数​​,它是Functionor的实例GeneratorFunction,而不是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-generator转换async为生成器函数,也可能用于transform-regenerator将生成器转换为常规函数。

async函数调用的结果是一个promise。根据提案,可以将Promise或非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 和 Zone.js Promise实现的已知问题)。即便如此,最好检测函数返回值不是预期的Promise实例,而不是检测函数是async,因为同样的问题适用于任何使用替代Promise实现的函数,而不仅仅是async所述 Angular 问题的解决方案是包装async返回值Promise.resolve)。

实践

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

一个可以返回Promise的函数

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

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

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

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

let result = await fnThatPossiblyReturnsAPromiseOrThrows();
...

一个应该返回Promise的函数

检查一个对象是否是一个 Promise 是一个单独的问题,但通常它不应该太严格或太松散以涵盖极端情况。instanceof Promise如果 globalPromise被替换,则可能无法工作Promise !== (async () => {})().constructor. 当 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函数不应与返回Promise的常规函数​​区分开来。没有可靠的方法和实际理由来检测非本地转译async函数。

我正在编写一个钩子函数,该函数接受一个对象、目标和钩子……我怎么知道我是否必须等待?
2021-04-16 11:50:09
@EstusFlask: stackoverflow.com/questions/10273309/... 看看我怎么不能只是“等待”...因为那样我就会改变钩子函数的语义。
2021-04-27 11:50:09
这对我不起作用。AsyncFunction !== Function即使我将关键字async作为参数传递给it()规范的函数也始终为 false 顺便说一下,我正在使用 Typescript。您能否看看这个问题并提供您的见解。我一直在尝试很多不同的方法,但还没有成功。:(
2021-04-28 11:50:09
@ErikAronesty 你能提供一个单行示例吗?如果一个值可以是Promise或不是Promise,你需要await,它适用于Promise和非Promise。这是答案中的最后一个片段显示的内容。
2021-05-01 11:50:09
@Tums 那是因为AsyncFunction !== Function检查是为了避免误报转译后的代码中不会有真正的正数,因为转译后的代码中的async函数与常规函数没有区别。
2021-05-09 11:50:09

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

theFunc.constructor.name == 'AsyncFunction'
当然@EstusFlask,你是完全正确的。如果是您的情况 - 您需要更复杂的解决方案。但是在“真实世界”(不是超级特殊或人工案例)中 - 可以使用此解决方案,而不是过度使用怪物跳棋。但是应该知道你在说什么,谢谢你的评论!
2021-04-19 11:50:09
这也有比字符串化更高性能的优势:)
2021-04-24 11:50:09
为什么=== 'AsyncFunction'不像@theVoogie 建议的那样使用
2021-05-03 11:50:09
鸭子类型的问题在于自定义函数通过了此检查,theFunc = new class AsyncFunction extends Function {}. 但是转译的async函数没有,theFunc = () => __awaiter(void 0, void 0, void 0, function* () { }).
2021-05-13 11:50:09
@Alexander,在现实世界中,非异步函数始终返回Promise,就像异步函数一样。
2021-05-14 11:50:09

@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*
    );
}

这是一个非常有效的问题,我很沮丧有人对他投了反对票。此类检查的主要用例是库/框架/装饰器。

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

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

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

使用本机 util 函数

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

但是,请记住上面的所有问题。一个偶然返回一个 promise 的函数,将返回一个假阴性。

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

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

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

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