Comment puis-je utiliser async / await au plus haut niveau?


185

Je suis passé en revue async/ awaitet après avoir parcouru plusieurs articles, j'ai décidé de tester les choses moi-même. Cependant, je n'arrive pas à comprendre pourquoi cela ne fonctionne pas:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

La console génère les éléments suivants (nœud v8.6.0):

> extérieur: [promesse d'objet]

> à l'intérieur: Hé là

Pourquoi le message de journal à l'intérieur de la fonction s'exécute-t-il ensuite? Je pensais que la raison async/ awaitavait été créée était pour effectuer une exécution synchrone à l'aide de tâches asynchrones.

Existe-t-il un moyen d'utiliser la valeur retournée dans la fonction sans utiliser un .then()après main()?


4
Non, seules les machines de temps peuvent rendre le code asynchrone synchrone. awaitn'est rien d'autre que du sucre pour la thensyntaxe de promesse .
Bergi

Pourquoi main renvoie-t-il une valeur? Si c'est le cas, ce n'est probablement pas le point d'entrée et doit être appelé par une autre fonction (par exemple async IIFE).
Estus Flask

@estus c'était juste un nom de fonction rapide pendant que je testais des choses dans le nœud, pas nécessairement représentatif d'un programmemain
Felipe

2
FYI, async/awaitfait partie de l'ES2017, pas de l'ES7 (ES2016)
Felix Kling

Réponses:


270

Je n'arrive pas à comprendre pourquoi cela ne fonctionne pas.

Parce que mainrenvoie une promesse; toutes les asyncfonctions le font.

Au niveau supérieur, vous devez soit:

  1. Utilisez une asyncfonction de niveau supérieur qui ne rejette jamais (sauf si vous souhaitez des erreurs de «rejet non géré»), ou

  2. Utilisez thenet catch, ou

  3. (Prochainement!) Utilisez le plus haut niveauawait , une proposition qui a atteint l'étape 3 dans le processus qui permet une utilisation de haut niveau awaitdans un module.

# 1 - De haut niveau async Fonction de qui ne rejette jamais

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

Remarquez le catch; vous devez gérer les rejets de promesse / exceptions asynchrones, car rien d'autre ne le fera; vous n'avez aucun appelant à qui les transmettre. Si vous préférez, vous pouvez le faire sur le résultat de son appel via la catchfonction (plutôt que try/ catchsyntax):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... ce qui est un peu plus concis (je l'aime pour cette raison).

Ou, bien sûr, ne gérez pas les erreurs et autorisez simplement l'erreur de «rejet non géré».

# 2 - thenetcatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

Le catchgestionnaire sera appelé si des erreurs se produisent dans la chaîne ou dans votre thengestionnaire. (Assurez-vous que votrecatch gestionnaire ne renvoie pas d'erreurs, car rien n'est enregistré pour les gérer.)

Ou les deux arguments pour then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

Notez à nouveau que nous enregistrons un gestionnaire de rejet. Mais sous cette forme, assurez-vous qu'aucun de vos thenrappels ne renvoie d'erreurs, rien n'est enregistré pour les gérer.

# 3 de haut niveau awaitdans un module

Vous ne pouvez pas utiliser awaitau niveau supérieur d'un script non-module, mais la proposition de niveau supérieurawait ( étape 3 ) vous permet de l'utiliser au niveau supérieur d'un module. C'est similaire à l'utilisation d'un asyncwrapper de fonction de niveau supérieur (n ° 1 ci-dessus) en ce sens que vous ne voulez pas que votre code de niveau supérieur rejette (renvoie une erreur) car cela entraînera une erreur de rejet non gérée. Donc, à moins que vous ne souhaitiez avoir ce rejet non géré lorsque les choses tournent mal, comme avec # 1, vous voudrez envelopper votre code dans un gestionnaire d'erreurs:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

Notez que si vous faites cela, tout module qui importe depuis votre module attendra que la promesse que vous êtes awaiten cours se concrétise; Lorsqu'un module utilisant le niveau supérieur awaitest évalué, il renvoie essentiellement une promesse au chargeur de module (comme le fait une asyncfonction), qui attend que cette promesse soit réglée avant d'évaluer les corps de tout module qui en dépend.


Penser cela comme une promesse explique maintenant pourquoi la fonction retourne immédiatement. J'ai expérimenté la création d'une fonction asynchrone anonyme de haut niveau et j'obtiens des résultats qui ont du sens maintenant
Felipe

2
@Felipe: Oui, async/ awaitsont du sucre syntaxique autour des promesses (le bon type de sucre :-)). Vous ne pensez pas simplement à cela comme à une promesse; c'est effectivement le cas. ( Détails .)
TJ Crowder

1
@LukeMcGregor - J'ai montré les deux ci-dessus, avec l' asyncoption tout d' abord. Pour la fonction de niveau supérieur, je peux le voir de toute façon (principalement à cause de deux niveaux d'indentation sur la asyncversion).
TJ Crowder

3
@Felipe - J'ai mis à jour la réponse maintenant que la awaitproposition de haut niveau a atteint l'étape 3. :-)
TJ Crowder

1
@SurajShrestha - Non. Mais ce n'est pas un problème que ce ne soit pas le cas. :-)
TJ Crowder

7

Top-Levelawait est passé à l'étape 3, donc la réponse à votre question Comment puis-je utiliser async / await au niveau supérieur? consiste simplement à ajouter awaitl'appel à main():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

Ou juste:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Gardez à l'esprit qu'il est toujours disponible uniquement dans Webpack@v5.0.0-alpha.15 .

Si vous utilisez TypeScript , il a atterri en 3.8 .

la v8 a ajouté un support dans les modules.

Il est également soutenu par Deno (comme commenté par gonzalo-bahamondez).


Plutôt cool. Avons-nous une feuille de route pour une implémentation de Node
Felipe

Je ne sais pas, mais il est très probable que nous verrons très bientôt une implémentation de TypeScript et Babel. L'équipe TypeScript a pour politique d'implémenter les fonctionnalités du langage de l'étape 3, et un plugin Babel est généralement construit dans le cadre du processus TC39 pour tester les propositions. Voir github.com/Microsoft/TypeScript/issues/…
Taro

Il est également disponible en deno (juste js, typéscript ne le prend toujours pas en charge github.com/microsoft/TypeScript/issues/25988 ) deno.land voir deno.news/issues/… .
Gonzalo Bahamondez

SyntaxError: await n'est valide que dans la fonction async
Sudipta Dhara

4

La vraie solution à ce problème est de l'aborder différemment.

Votre objectif est probablement une sorte d'initialisation qui se produit généralement au niveau supérieur d'une application.

La solution consiste à s'assurer qu'il n'y a qu'une seule instruction JavaScript au niveau supérieur de votre application. Si vous n'avez qu'une seule instruction en haut de votre application, alors vous êtes libre d'utiliser async / await à tout autre point n'importe où (sous réserve bien sûr des règles de syntaxe normales)

En d'autres termes, enveloppez tout votre niveau supérieur dans une fonction afin qu'il ne soit plus le niveau supérieur et cela résout la question de savoir comment exécuter async / wait au niveau supérieur d'une application - vous ne le faites pas.

Voici à quoi devrait ressembler le niveau supérieur de votre application:

import {application} from './server'

application();

1
Vous avez raison de dire que mon objectif est l'initialisation. Des choses telles que les connexions à la base de données, l'extraction de données, etc. Dans certains cas, il était nécessaire d'obtenir les données d'un utilisateur avant de passer au reste de l'application. Essentiellement, vous proposez queapplication() soit asynchrone?
Felipe

1
Non, je dis simplement que s'il n'y a qu'une seule instruction JavaScript à la racine de votre application, votre problème est résolu - l'instruction de niveau supérieur comme indiqué n'est pas asynchrone. Le problème est qu'il n'est pas possible d'utiliser async au niveau supérieur - vous ne pouvez pas attendre d'attendre réellement à ce niveau - donc s'il n'y a jamais qu'une seule instruction au niveau supérieur, vous avez contourné ce problème. Votre code asynchrone d'initialisation est maintenant en panne dans du code importé et donc async fonctionnera très bien, et vous pouvez tout initialiser au début de votre application.
Duke Dougal

1
CORRECTION - l'application EST une fonction asynchrone.
Duke Dougal

4
Je ne suis pas clair désolé. Le fait est qu'habituellement, au niveau supérieur, une fonction asynchrone n'attend pas ... JavaScript passe directement à l'instruction suivante, vous ne pouvez donc pas être certain que votre code d'initialisation est terminé. S'il n'y a qu'une seule déclaration en haut de votre application, cela n'a pas d'importance.
Duke Dougal

3

Pour donner plus d'informations en plus des réponses actuelles:

Le contenu d'un node.jsfichier est actuellement concaténé, à la manière d'une chaîne, pour former un corps de fonction.

Par exemple si vous avez un fichier test.js:

// Amazing test file!
console.log('Test!');

Puis node.jsconcaténera secrètement une fonction qui ressemble à:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

La principale chose à noter est que la fonction résultante n'est PAS une fonction asynchrone. Vous ne pouvez donc pas utiliser le termeawait directement à l'intérieur!

Mais disons que vous devez travailler avec les promesses de ce fichier, alors il existe deux méthodes possibles:

  1. Ne pas utiliser await directement dans la fonction
  2. Ne pas utiliser await

L'option 1 nous oblige à créer une nouvelle portée (et cette portée peut l'être async, car nous en avons le contrôle):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

L'option 2 nous oblige à utiliser l'API de promesse orientée objet (le paradigme moins joli mais tout aussi fonctionnel de travailler avec des promesses)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

J'espère personnellement que, si cela fonctionne, node.js concaténera par défaut le code dans une asyncfonction. Cela éliminerait ce mal de tête.


0

L'attente de niveau supérieur est une fonctionnalité du prochain standard EcmaScript. Actuellement, vous pouvez commencer à l'utiliser avec TypeScript 3.8 (en version RC pour le moment).

Comment installer TypeScript 3.8

Vous pouvez commencer à utiliser TypeScript 3.8 en l'installant à partir de npm à l'aide de la commande suivante:

$ npm install typescript@rc

À ce stade, vous devez ajouter la rcbalise pour installer la dernière version de typescript 3.8.


Mais vous devrez alors expliquer comment l'utiliser?
raarts

-2

Puisque main()s'exécute de manière asynchrone, il renvoie une promesse. Vous devez obtenir le résultat dans la then()méthode. Et parce que les then()retours sont prometteurs aussi, vous devez appeler process.exit()pour mettre fin au programme.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )

2
Faux. Une fois que toutes les promesses ont été acceptées ou rejetées et que plus aucun code n'est en cours d'exécution dans le thread principal, le processus se termine de lui-même.

@Dev: normalement, vous souhaitez passer des valeurs différentes à exit()pour signaler si une erreur s'est produite.
9000

@ 9000 Oui, mais cela ne se fait pas ici, et comme un code de sortie de 0 est la valeur par défaut, il n'est pas nécessaire de l'inclure

@ 9000 en fait, le gestionnaire d'erreurs devrait probablement utiliserprocess.exit(1)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.