Как узнать, является ли функция асинхронной? - программирование

Как узнать, является ли функция асинхронной?

Мне нужно передать функцию другой функции и выполнить ее как обратный вызов. Проблема в том, что иногда эта функция является асинхронной, например:

async function() {
 // Some async actions
}

Итак, я хочу выполнить await callback() или callback() в зависимости от типа получаемой функции.

Есть ли способ узнать тип функции?

4b9b3361

Ответ 1

Собственные async функции могут быть идентифицированы при преобразовании в строки:

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

Или с 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 функции Babel, которая использует 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 вместо обнаружения того, что функция async, потому что та же проблема применима к любой функции, которая использует альтернативную реализацию обещания, а не только async (решение указанной угловой проблемы обернуть async возвращаемое значение с Promise.resolve).

TL; DR: async функции не следует отличать от обычных функций, которые возвращают обещания. Их, конечно, не следует отличать в такой ситуации, как эта. Не существует надежного способа и причины обнаружения несобственных переносимых async функций.

Ответ 2

Оба @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.

Ответ 3

Я предпочитаю этот простой способ:

theFunc.constructor.name == 'AsyncFunction'

Ответ 4

Если вы используете NodeJS 10.x или выше

Используйте встроенную функцию утилит.

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

Имейте в виду все проблемы от вышеперечисленных. Функция, которая просто случайно возвращает обещание, вернет ложный минус.

И вдобавок ко всему (из документов):

Обратите внимание, что это только сообщает о том, что видит движок JavaScript; в частности, возвращаемое значение может не совпадать с исходным исходным кодом, если использовался инструмент для переноса.

Но если вы используете async в NodeJS 10 и не transiplation. Это хорошее решение.

Ответ 5

TL; DR

Краткий ответ: используйте instaceof после воздействия AsyncFunction - см. Ниже.

Длинный ответ: Не делайте этого - см. Ниже.

Как это сделать

Вы можете определить, была ли функция объявлена с помощью ключевого слова async

Когда вы создаете функцию, она показывает, что это тип Функция:

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

Вы можете проверить его с помощью оператора instanceof:

> f1 instanceof Function
true

Когда вы создаете функцию async, она показывает, что это тип 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 но помните, что действительно важно не то, как была создана функция, но независимо от того, возвращает ли функция обещание.

Везде, где вы можете использовать эту функцию "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, но используйте ее с осторожностью, потому что, когда вы ее проверяете, скорее всего, вы делаете что-то неправильно.

Ответ 6

Кажется, что 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()

Ответ 7

В начале вы можете предположить, что обратный вызов является обещанием:

export async function runSyncOrAsync(callback: Function) {

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

и их в вашем коде вы можете сделать это:

await runSyncOrAsync(callback)

которая решит вашу проблему с неизвестным типом обратного вызова....

Ответ 8

Hei,

Вот подход, предоставленный Дэвидом Уолшем в его посте:

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

Ура!