119

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

async function() {
 // Some async actions
}

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

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

4

9 回答 9

97

理论

本机函数在转换为字符串时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 的,所以问题可能是指 Babel 实现的async函数,它依赖于transform-async-to-generator转译async为生成器函数,也可能用于transform-regenerator将生成器转译为常规函数。

async函数调用的结果是一个承诺。根据proposal,可以将promise 或non-promise 传递给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实例而不是检测函数是async,因为同样的问题适用于任何使用替代承诺实现的函数,而不仅仅是async所述Angular问题的解决方案是包装async返回值Promise.resolve)。

实践

从表面上看,asyncfunction 只是一个无条件返回原生 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();
...

一个应该返回一个承诺的函数

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

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

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函数。

于 2016-07-21T17:08:02.110 回答
39

只要只使用本机异步函数(通常是这种情况),我更喜欢这种简单的方式:

theFunc.constructor.name == 'AsyncFunction'
于 2018-08-19T19:33:03.153 回答
30

@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问题投反对票。

于 2016-11-11T01:15:22.737 回答
16

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

使用本机 util 函数

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

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

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

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

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

于 2018-12-28T12:13:51.400 回答
11

似乎也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()
于 2018-07-15T15:05:45.477 回答
8

这是 David Walsh 在他的博文中提供的一个简短而有用的方法

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

干杯!

于 2019-04-30T06:37:10.520 回答
4

TL;博士

简短的回答:暴露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关键字创建了函数,但请谨慎使用,因为当您检查它时,很可能您做错了什么。

于 2018-03-08T06:34:20.367 回答
1

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

export async function runSyncOrAsync(callback: Function) {

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

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

await runSyncOrAsync(callback)

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

于 2018-12-26T01:59:09.863 回答
1

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

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

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

如果您需要一个辅助函数来确定一个值是否是异步函数,而不调用它,或者如果一个值是一个返回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,我们可以返回上述值,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

于 2021-12-27T18:00:50.857 回答