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

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

async function() {
// Some async actions
}

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

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

48372 次浏览

理论

原生 async功能可能是可识别的 当转换为字符串时:

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函数调用的结果是一个承诺。根据提案,一个承诺或一个非承诺可以传递给 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(角度问题的解决方案是用 Promise.resolve包装 async返回值)。

练习

从外部来看,async函数只是一个无条件返回本机承诺的函数,因此它应该被当作一个函数来对待。即使一个函数曾经被定义为 async,它也可以在某个时刻被转换成正则函数。

一个可以返回承诺的函数

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

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


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

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

let result = await fnThatPossiblyReturnsAPromiseOrThrows();
...

应该返回承诺的函数

检查一个对象是否是一个承诺是 另一个问题,但一般来说,它不应该太严格或松散,以涵盖角落情况。如果取代了全局 PromisePromise !== (async () => {})().constructor,则 instanceof Promise可能无法工作。这可能发生在角度和非角度应用程序接口。

一个需要 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');
}

DR: 不应将 async函数与返回承诺的常规函数区分开来。没有可靠的方法和实际的理由来检测非本地传输的 async函数。

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

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

现在还为时尚早,我们不应该对 有效的问题投反对票。

DR

简短的回答: 在 暴露 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关键字创建的,但请记住,真正重要的不是函数是如何创建的,而是函数是否返回承诺。

你可以在任何地方使用这个“异步”函数:

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

你也可以用这个:

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

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

具体来说,考虑以下情况:

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


const f2 = () => f1(123);

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

摘要

因此,检查函数是否使用 async关键字创建是可能的,但要小心使用,因为当您检查它时,很可能您正在做一些错误的事情。

看来 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()

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

theFunc.constructor.name == 'AsyncFunction'

一开始,你可以假设回调是有希望的:

export async function runSyncOrAsync(callback: Function) {


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

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

await runSyncOrAsync(callback)

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

以防您使用 NodeJS 10.x 或更高版本

使用 原生 util 函数原生 util 函数

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

尽管如此,请记住上述答案中的所有问题。只是偶然返回一个承诺的函数,将返回一个错误的否定值。

最重要的是(来自医生的) :

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

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

下面是 David Walsh 在他的 博客文章中提供的一个简短而有用的方法:

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

干杯!

完整解决方案: 同时处理异步和承诺

我总是使用承诺和异步/等待交换,因为它们基本上是一样的。

Async/Await 用于处理异步函数中的承诺。它基本上是承诺的语法糖。它只是一个重新设计代码并使承诺更易于阅读和使用的包装器。 资料来源: http://www.GeeksForGeeks.org/different-between-諾-and-sync-waiting-in-node-js/”rel = “ nofollow noReferrer”> GeeksForGeeks

如果您需要一个辅助函数来确定一个值是否是 异步函数,而不调用它,或者一个值是否是返回 我保证的函数,那么您已经到达了正确的位置。

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

  1. 检查函数是否是异步/等待函数。
  2. 检查一个正则函数是否返回一个  。
  3. 都检查一下。

处理异步函数

此函数可以确定是否使用 async关键字定义了函数。

要验证的函数示例

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

验证功能

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

履行承诺

这个函数可以确定一个正则函数是否返回一个 我保证。为了评估给定的函数是否返回一个  诺言,我们需要调用该函数并检查返回的值。为了避免对同一个函数进行多次调用,如果返回的值是    我们可以返回上述值,如果不是,返回 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
}

两个都要处理

因为 异步功能我保证在本质上是相同的,所以我们可以检查它们是否都返回一个  。

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
}

结论

异步函数的验证速度更快,也更简单,而承诺函数需要调用才能进行验证。

测试函数(CodePen)

Https://codepen.io/isakhauge/pen/goggqpy

因此,我希望根据 它正在接收的函数类型。

你总是可以用 await执行它,它会做正确的事情:

async function main(callback) {
let result = await callback(); // even if callback is not async
// use 'result'
}

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

也许您真正感兴趣的是函数的 结果类型。达留什•菲利皮亚克(Dariusz Filipiak)的回答很好,但可以更简洁:

async function main(callback) {
let result = callback();
if (result instanceof Promise) {
result = await result;
}
// use 'result'
}