web-dev-qa-db-fra.com

La fonction async ne renvoie pas de valeur, mais console.log () fait: comment faire?

J'ai une classe es6, avec une méthode init() chargée de récupérer les données, de les transformer, puis de mettre à jour la propriété this.data de la classe avec les données récemment transformées. Jusqu'ici tout va bien ... La classe elle-même a une autre méthode getPostById(), pour faire ce que cela ressemble. Voici le code pour la classe:

class Posts {
  constructor(url) {
    this.ready = false
    this.data = {}
    this.url = url
  }
  async init() {
      try { 
        let res = await fetch( this.url )
        if (res.ok) {
            let data = await res.json()

          // Do bunch of transformation stuff here

          this.data = data
          this.ready = true
            return data
        }
      } 
      catch (e) { 
         console.log(e)
      }
  }
  getPostById(id){
     return this.data.find( p => p.id === id )
  }
}  

Simplement, sauf que j'ai un mécanisme async/await dans la méthode init() . Maintenant, ce code fonctionnera correctement:

let allPosts = new Posts('https://jsonplaceholder.typicode.com/posts')

allPosts.init()
        .then( d => console.log(allPosts.getPostById(4)) )
// resulting Object correctly logged in console

mais cela n’est imprimé que dans la console: Comment utiliser allPosts.getPostById(4) en tant que return d’une fonction? 

Comme:

let myFunc = async () => {
   const postId = 4
   await allPosts.init()  // I need to wait for this to finish before returning

   // This is logging correct value
   console.log( 'logging: ' + JSON.stringify(allPosts.getPostById( postId ), null, 4) )

   // How can I return the RESULT of allPosts.getPostById( postId ) ???
   return allPosts.getPostById( postId )
}

myFunc() renvoie Promise mais pas la valeur finale. J'ai lu plusieurs articles sur le sujet, mais ils donnent tous un exemple de journalisation, qui ne revient jamais.

Voici un violon qui inclut deux manières de manipuler init(): en utilisant Promise et en utilisant async/await. Quoi que j'essaye, je ne parviens pas à UTILISER la valeur finale de getPostById(id).

La question de ce post est: comment puis-je créer une fonction qui va retourner la valeur de getPostById(id)?

MODIFIER:

Beaucoup de bonnes réponses tentent d'expliquer ce que sont les promesses en ce qui concerne la boucle d'exécution principale. Après beaucoup de vidéos et d’autres bonnes lectures, voici ce que je comprends maintenant: 

ma fonction init() retourne correctement. Cependant, dans la boucle d'événement principale: il retourne a Promise, puis il est de mon devoir de récupérer le résultat de cette promesse à partir d'une boucle parallèle kinda (pas un nouveau thread réel). Pour saisir le résultat de la boucle parallèle, il y a deux façons: 

  1. utiliser .then( value => doSomethingWithMy(value) )

  2. utilisez let value = await myAsyncFn(). Maintenant, voici le hoquet idiot: 

wait ne peut être utilisé que dans une fonction async: p

revenant ainsi elle-même une promesse, utilisable avec await qui devrait être incorporée dans une fonction async, qui sera utilisable avec await etc ...

Cela signifie que nous ne pouvons pas vraiment attendre une promesse: nous devrions plutôt attraper indéfiniment la boucle parallèle: en utilisant .then() ou async/await.

Merci pour l'aide !

4
Jona Rodrigues

Quant à ton commentaire; Je vais l'ajouter comme réponse.

Le code que vous écrivez en JavaScript est exécuté sur un seul thread, ce qui signifie que si votre code pouvait réellement attendre quelque chose, il bloquerait l'exécution de tout autre code. La boucle d’événement de JavaScript est très bien expliquée dans cette vidéo et si vous aimez lire dans cette page .

alert("cannot do anything until you click ok"); est un bon exemple de code bloquant dans le navigateur. Alert bloque tout, l'utilisateur ne peut même pas faire défiler ou cliquer sur quoi que ce soit dans la page et votre code bloque également l'exécution.

Promise.resolve(22)
.then(x=>alert("blocking")||"Hello World")
.then(
  x=>console.log(
    "does not resolve untill you click ok on the alert:",
    x
  )
);

Exécutez cela dans une console et vous voyez ce que je veux dire par blocage.

Cela crée un problème lorsque vous voulez faire quelque chose qui prend du temps. Dans d'autres frameworks, vous utiliseriez un thread ou des processus, mais ce n'est pas le cas dans JavaScript (techniquement, il existe un outil Web et un fork dans un nœud, mais c'est une autre histoire, bien plus compliquée que d'utiliser des api asynchrones).

Donc, lorsque vous voulez faire une requête http, vous pouvez utiliser fetch mais la récupération prend du temps et votre fonction ne doit pas bloquer (doit renvoyer quelque chose aussi rapidement que possible). C'est pourquoi chercher va retourner une promesse. 

Notez que fetch est implémenté par le navigateur/noeud et s'exécute dans un autre thread. Seul le code que vous écrivez s'exécute dans un seul thread. Par conséquent, le fait de lancer beaucoup de promesses selon lesquelles seul le code que vous écrivez n'accélérera rien, mais appeler en parallèle des api asynchrones natives.

Avant promesses, le code asynchrone utilisait des rappels ou renverrait un objet observable (comme XmlHttpRequest), mais couvrons les promesses puisque vous pouvez convertir le code plus traditionnel en promesse de toute façon.

Une promesse est un objet qui a une fonction then (et un tas de choses qui sont du sucre pour, mais fait la même chose), cette fonction prend 2 paramètres.

  1. Gestionnaire de résolution: une fonction qui sera appelée par la promesse lorsque celle-ci sera résolue (ne comporte aucune erreur et est terminée). La fonction recevra un argument avec la valeur de résolution (pour les demandes http, il s'agit généralement de la réponse).
  2. Gestionnaire de rejet: Une fonction qui sera appelée par la promesse lorsque la promesse est rejetée (comporte une erreur). Cette fonction recevra un argument, c’est généralement l’erreur ou la raison du rejet (cela peut être une chaîne, un nombre ou autre).

Conversion du rappel en promesse.

Les API traditionnelles (en particulier les API de nœuds) utilisent des rappels:

traditionalApi(
  arg
  ,function callback(err,value){ 
    err ? handleFail(err) : processValue(value);
  }
);

Cela rend difficile pour le programmeur de détecter les erreurs ou de traiter la valeur de retour de manière linéaire (de haut en bas). Il devient encore plus impossible d'essayer de faire les choses en parallèle ou en parallèle avec la gestion des erreurs (impossible à lire).

Vous pouvez convertir les API traditionnelles en promesses avec new Promise

const apiAsPromise = arg =>
  new Promise(
    (resolve,reject)=>
      traditionalApi(
        arg,
        (err,val) => (err) ? reject(err) : resolve(val)
      )
  )

async wait

C'est ce qu'on appelle le sucre de syntaxe pour les promesses. Cela donne aux fonctions qui consomment des promesses un aspect plus traditionnel et plus facile à lire. C'est-à-dire que si vous aimez écrire du code traditionnel, je dirais que la composition de petites fonctions est beaucoup plus facile à lire. Par exemple, pouvez-vous deviner ce que cela fait ?:

const handleSearch = search =>
  compose([
    showLoading,
    makeSearchRequest,
    processRespose,
    hideLoading
  ])(search)
  .then(
    undefined,//don't care about the resolve
    compose([
      showError,
      hideLoading
    ])
  );

Anayway; assez de discours. La partie importante est de comprendre que async await ne démarre pas réellement un autre thread, les fonctions async renvoient toujours une promesse et que await ne bloque ni n'attend réellement. C'est la syntaxe sugar pour someFn().then(result=>...,error=>...) et elle ressemble à:

async someMethod = () =>
  //syntax sugar for:
  //return someFn().then(result=>...,error=>...)
  try{
    const result = await someFn();
    ...
   }catch(error){
     ...
   }
}

Les exemples montrent toujours try catch mais vous n’avez pas besoin de le faire, par exemple:

var alwaysReject = async () => { throw "Always returns rejected promise"; };
alwaysReject()
.then(
  x=>console.log("never happens, doesn't resolve")
  ,err=>console.warn("got rejected:",err)
);

Toute erreur renvoyée ou await renvoyant une promesse refusée fera en sorte que la fonction asynchrone renvoie une promesse refusée (à moins que vous n'essayiez de l'attraper). Plusieurs fois, il est souhaitable de le laisser échouer et de laisser l’appelant gérer les erreurs. 

Il peut être nécessaire de corriger les erreurs si vous souhaitez que la promesse porte ses fruits avec une valeur spéciale pour les promesses rejetées, afin que vous puissiez la gérer plus tard, mais que la promesse ne soit pas techniquement rejetée, elle sera toujours résolue.

Un exemple est Promise.all, cela prend un tableau de promesses et renvoie une nouvelle promesse qui se résout en un tableau de valeurs résolues ou à rejeter lorsque l’une d’elles rejette. Vous voudrez peut-être simplement récupérer les résultats de toutes les promesses et filtrer celles qui ont été rejetées:

const Fail = function(details){this.details=details;},
isFail = item => (item && item.constructor)===Fail;
Promise.all(
  urls.map(//map array of urls to array of promises that don't reject
    url =>
      fetch(url)
      .then(
        undefined,//do not handle resolve yet
        //when you handle the reject this ".then" will return
        //  a promise that RESOLVES to the value returned below (new Fail([url,err]))
        err=>new Fail([url,err])
      )
  )
)
.then(
  responses => {
    console.log("failed requests:");
    console.log(
      responses.filter(//only Fail type
        isFail
      )
    );
    console.log("resolved requests:");
    console.log(
      responses.filter(//anything not Fail type
        response=>!isFail(response)
      )
    );
  }
);
7
HMR

NOTE: Où que vous utilisiez await, il doit figurer dans une fonction async.

Découvrez le FIDDLE MISE À JOUR

Vous devez utiliser await myFunc() pour obtenir la valeur que vous attendez de getPostById car une fonction asynchrone renvoie toujours une promesse. 

C’est parfois très frustrant de devoir convertir toute la chaîne en fonctions async, mais c’est le prix à payer pour la convertir en code synchrone, je suppose. Je ne sais pas si cela peut être évité, mais je souhaite entendre des personnes plus expérimentées dans ce domaine.

Essayez le code ci-dessous dans votre console en copiant les fonctions, puis en accédant à final et await final.

REMARQUE: 

Une fonction asynchrone PEUT contenir une expression en attente. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Aucune règle n’est à attendre pour pouvoir même déclarer une fonction asynchrone. L'exemple ci-dessous utilise une fonction asynchrone sans wait juste pour montrer qu'une fonction asynchrone retourne toujours une promesse.

const sample = async () => {
  return 100;
}

// sample() WILL RETURN A PROMISE AND NOT 100
// await sample() WILL RETURN 100

const init = async (num) => {
  return new Promise((resolve, reject) => {
    resolve(num);
  });
}

const myFunc = async (num) => {
  const k = await init(num);
  return k;
}

// const final = myFunc();
// final; This returns a promise
// await final; This returns the number you provided to myFunc

0
Nandu Kalidindi