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?
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.
@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.
Je préfère cette manière simple:
theFunc.constructor.name == 'AsyncFunction'
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.
Réponse courte: Utilisez instaceof
après exposantAsyncFunction
- voir ci-dessous.
Réponse longue: ne faites pas cela - voir ci-dessous.
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
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.
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.
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()
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 ....