web-dev-qa-db-fra.com

Comment savoir si une fonction est asynchrone?

Je dois passer une fonction à une autre fonction et l'exécuter en tant que rappel. Le problème est que parfois cette fonction est async, comme:

async function() {
 // Some async actions
}

Je souhaite donc exécuter await callback() ou callback() en fonction du type de fonction reçu.

Existe-t-il un moyen de connaître le type de la fonction?

37
Facundo Matteo

Les fonctions async natives peuvent être identifiables lors de la conversion en chaînes :

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

Ou par AsyncFunction constructeur:

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

asyncFn instanceof AsyncFunction === true

Cela ne fonctionnera pas avec la sortie Babel/TypeScript, car asyncFn est une fonction normale dans du code transpilé, il s'agit d'une instance de Function ou GeneratorFunction, pas AsyncFunction. Pour vous assurer qu'il ne donnera pas faux positifs pour le générateur et les fonctions normales en code transpilé

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

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

La question concerne évidemment la mise en oeuvre Babel de la fonction async, qui repose sur transform-async-to-generator pour transpiler async en fonctions de générateur, peut également utiliser transform-regenerator pour transpiler le générateur en fonctions normales.

Le résultat de l'appel de la fonction async est une promesse. Selon la proposition , une promesse ou une non-promesse peut être passée à await, donc await callback() est universel.

Il y a seulement quelques cas Edge où cela peut être nécessaire. Par exemple, les fonctions async natives utilisent les promesses natives en interne et ne récupèrent pas la variable Promise globale si sa mise en œuvre a été modifiée:

let NativePromise = Promise;
Promise = CustomPromiseImplementation;

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

Cela peut affecter le comportement de la fonction (il s'agit d'un problème connu pour implémentation angulaire et Zone.js promise ). Même dans ce cas, il est préférable de détecter que la valeur renvoyée par la fonction n’est pas une instance Promise attendue plutôt que de détecter qu’une fonction est async, car le même problème s’applique à toute fonction utilisant une implémentation de promesse alternative, et pas seulement async ( la solution à Problème angulaire doit emballer la valeur de retour async avec Promise.resolve).

TL; DR: async Les fonctions ne doivent pas être distinguées des fonctions classiques qui renvoient des promesses. Ils ne devraient certainement pas être distingués dans une situation comme celle-ci. Il n'y a pas de moyen fiable ni de raison de détecter les fonctions async transpilées non natives.

37
estus

@Rnd et @estus sont corrects.

Mais pour répondre à la question avec une solution de travail réelle ici vous allez

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

C'est une question très valable et je suis fâché que quelqu'un l'ait voté à la baisse. Le cas d'utilisation principal pour ce type de vérification concerne une bibliothèque/un cadre/des décorateurs.

Ce sont les premiers jours, et nous ne devrions pas voterVALIDEquestions.

13
Chad Scira

Je préfère cette manière simple:

theFunc.constructor.name == 'AsyncFunction'
8
Alexander

Si vous utilisez NodeJS 10.x ou une version ultérieure

Utilisez la fonction native util .

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

Gardez à l'esprit toutes les préoccupations des personnes âgées. Une fonction qui ne fait que renvoyer accidentellement une promesse renverra un faux négatif.

Et en plus de cela (de la documentation):

Notez que cela ne fait que rendre compte de ce que voit le moteur JavaScript. en particulier, la valeur de retour peut ne pas correspondre au code source d'origine si un outil de transpilation a été utilisé.

Mais si vous utilisez async dans NodeJS 10 et qu’il n’ya pas de transiplation. C'est une bonne solution.

4
Ian Segers

TL; DR

Réponse courte: Utilisez instaceof après exposantAsyncFunction - voir ci-dessous.

Réponse longue: ne faites pas cela - voir ci-dessous.

Comment faire

Vous pouvez détecter si une fonction a été déclarée avec le mot clé async

Lorsque vous créez une fonction, cela montre qu'il s'agit d'un type Function:

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

Vous pouvez le tester avec l'opérateur instanceof:

> f1 instanceof Function
true

Lorsque vous créez une fonction asynchrone, cela indique qu'il s'agit d'un type AsyncFunction:

> f2 = async function () {}
[AsyncFunction: f2]

on peut donc s’attendre à ce qu’il puisse également être testé avec instanceof:

> f2 instanceof AsyncFunction
ReferenceError: AsyncFunction is not defined

Pourquoi donc? Parce que la fonction AsyncFunction n'est pas un objet global. Voir les docs:

même si, comme vous pouvez le constater, il est répertorié sous Reference/Global_Objects...

Si vous avez besoin d'un accès facile à la AsyncFunction, vous pouvez utiliser mon module unexposed:

pour obtenir soit une variable locale:

const { AsyncFunction } = require('unexposed');

ou pour ajouter une variable globale AsyncFunction à côté d'autres objets globaux:

require('unexposed').addGlobals();

et maintenant ce qui précède fonctionne comme prévu:

> f2 = async function () {}
[AsyncFunction: f2]
> f2 instanceof AsyncFunction
true

Pourquoi tu ne devrais pas le faire

Le code ci-dessus testera si la fonction a été créée avec le mot clé async, mais gardez à l'esprit que ce qui est vraiment important n'est pas la façon dont une fonction a été créée, mais si une fonction renvoie ou non une promesse.

Partout où vous pouvez utiliser cette fonction "asynchrone":

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

vous pouvez aussi utiliser ceci:

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

même s'il n'a pas été créé avec le mot clé async et ne sera donc pas associé à instanceof ni à à aucune autre méthode publiée dans d'autres réponses.

Plus précisément, considérez ceci:

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

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

Le f2 est juste f1 avec un argument codé en dur et il n’a pas de sens d’ajouter async ici, même si le résultat sera autant "asynchrone" que f1 à tous égards.

Résumé

Il est donc possible de vérifier si une fonction a été créée avec le mot clé async, mais utilisez-la avec prudence, car si vous la vérifiez, il est fort probable que vous fassiez quelque chose de mal.

2
rsp

Il semble que await puisse également être utilisé pour des fonctions normales. Je ne sais pas si cela peut être considéré comme une "bonne pratique", mais voici:

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()
1
gyo

Vous pouvez supposer au début que le rappel est une promesse:

export async function runSyncOrAsync(callback: Function) {

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

et les dans votre code, vous pouvez le faire:

await runSyncOrAsync(callback)

ce qui résoudra votre problème avec le type de rappel non connu ....

0
Dariusz Filipiak