Journal recherche-totoz en JavaScript

Posté par .
Tags : aucun
8
30
nov.
2018

En discutant avec plusieurs personnes, j'ai constaté qu'ils pensaient que JavaScript était encore pénible à utiliser, en particulier en ce qui concerne l'appel d'APIs asynchrones et le "callback hell".

Pour illustrer le sujet, j'ai fait un petit programme qui recherche des totoz sur totoz.eu et affiche leur nom sur le terminal.

Le programme doit :

Ecrire le programme ne présente aucune difficulté, mais selon le langage cela peut être un peu pénible.

Avant de vous montrer le programme, un petit rappel sur la concurrence en JavaScript :

  • Un programme JavaScript est mono-threadé (il y a des façons de passer outre mais ce n'est pas le modèle "normal" d'utilisation)
  • Pour ne pas tout bloquer dès qu'on appelle une fonction qui prend du temps, la plupart des APIs sont asynchrones : elles retournent immédiatement, sans attendre que le traitement soit fini.
  • Traditionnellement, pour utiliser le résultat du traitement, il fallait passer un callback à la fonction, qui était exécuté une fois le résultat disponible
  • Aujourd'hui, on peut utiliser des fonctions dites asynchrones (mot-clé async), dont la syntaxe ressemble à celle des fonctions normales, mais qui peuvent appeler d'autres fonctions asynchrones et n'exécuter l'instruction suivante qu'une fois le résultat des autres fonctions disponible, tout en rendant le contrôle le temps que les autres fonctions s'exécutent.

Voilà donc le programme (et le projet complet sur github) :

const fetch = require('node-fetch')
const xmljs = require('xml-js')

async function searchTotoz(query) {
    const res = await fetch(
        'https://totoz.eu/search.xml?terms=' + 
        encodeURIComponent(query))
    const xml = await res.text()

    const obj = xmljs.xml2js(xml,{compact: true})

    const totozList = obj.totozes.totoz.length ? obj.totozes.totoz : [obj.totozes.totoz]
    const totozNamesStr = totozList.map(t => t.name._text).join('\n')

    console.log(totozNamesStr)
}

if (process.argv[2]) {
    searchTotoz(process.argv[2])
        .catch(err => console.error(err.message))
}
else
    console.error("Syntax: npm start QUERY")

Et vous, comment écririez-vous ce programme dans votre langage favori (en faisant la requête HTTP de façon asynchrone) ?

  • # Tu tiens pas tes promesses

    Posté par (page perso) . Évalué à -3 (+3/-7). Dernière modification le 30/11/18 à 14:01.

    Ce n'est pas asynchrone (désolé, en vrai ça l'est) ça n'est pas fluide à la lecture, tu mets des await (et tu force le blocage du code en attente de la réponse) (en vrai j'imagine que la VM js va aller lancer d'autres en attendant).

    La bonne methode serait d'utiliser l'API des promesses, du style:

        const fetch = require('node-fetch')
        const xmljs = require('xml-js')
    
        if (process.argv[2]) {
            fetch(
                'https://totoz.eu/search.xml?terms=' + 
                encodeURIComponent(process.argv[2])
            )
            .then(ret => res.text())
            .then(ret => {
                obj = xmljs.xml2js(xml,{compact: true})
                const totozList = obj.totozes.totoz.length ? obj.totozes.totoz : [obj.totozes.totoz]
                const totozNamesStr = totozList.map(t => t.name._text).join('\n')
                console.log(totozNamesStr)
            })
            .catch(err => console.error(err.message))
            ;
        }

    (non testé)

    • [^] # Re: Tu tiens pas tes promesses

      Posté par (page perso) . Évalué à 6 (+4/-0).

      J'ai du mal à me départager quant à ma compréhension de ton commentaire, soit :

      1. Tu n'as pas compris que async/await est grosso-modo du sucre syntaxique au dessus des promesses
      2. Tu le sais très bien mais ton opinion c'est que les fonctions async sont plus pénibles à comprendre que l'écriture sous forme de promesses
      • [^] # Re: Tu tiens pas tes promesses

        Posté par (page perso) . Évalué à 2 (+4/-3). Dernière modification le 30/11/18 à 15:09.

        En réalité je l'ai très bien compris, que c'est juste du sucre syntaxique, mais en vrai c'est pas complètement pareil (tout dépend comment sont utilisés les promesses, chaînées ou non, etc) j'ai juste corrigé mon post entre temps (à force de pratiquer plein de langages différents on fini par s'embrouiller) d'où d'ailleurs mes raturages − n'aimant pas cacher mes erreurs, je préfère les assumer.

        Ceci dit, je trouve la solution des promesses bien plus élégante à lire, et je pratique cette tournure plutôt que le await/async. D'un certain côté, une fois que t'as commencé à partir dans le fameux "promise hell", si ton code n'est pas bien structuré, ça devient vraiment un enfer, mais si ton code est bien structuré, c'est très élégant: avec ça tu détecte les conneries architecturales assez rapidement.

        Mais les JS-eux adorent await/async, beaucoup n'aiment pas les promises, malgré mon petit caffouillage, ça ne méritait pas forcément de passer dans le négatif, c'est une alternative à la fois tout aussi élégante, mais aussi dans certains cas plus pertinent. De plus, ça force à écrire les fameuses callback dont parle l'auteur, mais avec une variante en utilisant la "arrow syntax" (qui ne propage pas le this dans les callback), qui est courte et élégante.

        Avec cette syntaxe, une autre chose que j'aime bien: on a pas besoin de déclarer ses variables (const toto) vu qu'on les récupère directement en paramètre des closures et on peut arriver à un stade où on peut considérer que déclarer des variables est une erreur de programmation: on rajoute des embranchements et la complexité cyclomatique et donc on augmente la probabilité d'avoir des bugs (certains analyseurs statiques d'ailleurs mettent ça en valeur).

        Par ailleurs, avec les promesses, on ne mélange pas les scopes, et donc on ne partage pas les variables: on réduit par la même occasion les chances d'écrasement ou d'utilisation de variable accidentels de par cette isolation, le code est potentiellement plus robuste (pas forcément plus robuste à l'exécution, mais plus résistant à l'erreur humaine).

        • [^] # Re: Tu tiens pas tes promesses

          Posté par (page perso) . Évalué à 4 (+2/-0).

          Par ailleurs, avec les promesses, on ne mélange pas les scopes, et donc on ne partage pas les variables: on réduit par la même occasion les chances d'écrasement ou d'utilisation de variable accidentels de par cette isolation, le code est potentiellement plus robuste (pas forcément plus robuste à l'exécution, mais plus résistant à l'erreur humaine).

          Je comprends ton point de vue, mais j'ai toujours trouvé ultra relou de transmettre une variable par le biais des enchaînements de retour de promesses et j'ai très souvent besoin de le faire, et je ne pense pas que c'est nécessairement une mauvaise pratique que d'avoir besoin d'accéder à une variable à plusieurs étages de son traitement asynchrone.

          Après, il est vrai que l'isolation "cognitive" du code asynchrone est moins simple avec du async/await.

          • [^] # Re: Tu tiens pas tes promesses

            Posté par . Évalué à 3 (+1/-0).

            C'est un changement qui peut paraître anodin, voir juste une contrainte, mais c'est bien plus que ça. Tu passe de l'utilisation d'une mémoire partagée à un passage de message entre tes traitements. Ça change pas mal de propriété de ton code (possibilité d'écrire des fonctions pures, robustesse au modèle d'exécution, etc). C'est pour ça qu'à titre perso, je trouve que la petite gène vaut largement les gains.

            • [^] # Re: Tu tiens pas tes promesses

              Posté par (page perso) . Évalué à 2 (+1/-1).

              En javascript, si tu fais :

              maFunc1()
                .then(() => {
                  const res = [1, 2];
                  return res;
                })
                .then(res => {
                  res.push(3);
                });

              Tu n'as pas gagné l'immutabilité hein.

              Dans l'écriture async/await, on a même plus les fonctions de l'écriture traditionnelle des promesses, donc c'est difficile de commencer à parler de fonctions "pures" dans ce cas là.

              D'expérience, je trouve qu'à condition de garder les fonctions async/await courtes et avec une bonne séparation des responsabilités, on y gagne plutôt globalement en légèreté d'écriture.

              • [^] # Re: Tu tiens pas tes promesses

                Posté par . Évalué à 2 (+0/-0).

                Bien sûr que tu n'a pas un langage pur, ce n'est pas le question. Tu as des pratiques qui poussent à écrire des fonctions pures, même si ce n'est pas obligatoire. https://linuxfr.org/users/barmic/journaux/adopter-un-style-de-programmation-fonctionnel

                Je n'ai pas compris le rapport avec async/await par contre ?

                • [^] # Re: Tu tiens pas tes promesses

                  Posté par (page perso) . Évalué à 3 (+1/-0). Dernière modification le 01/12/18 à 09:40.

                  Je n'ai pas compris le rapport avec async/await par contre ?

                  Bin notre discussion portait sur le choix (ou non) de la forme async/await par rapport à la forme des promesses traditionnelles.

                  Lorsqu'on choisit async/await, on a plus vraiment de fonctions, contrairement aux enchaînements de promesses, puisqu'on écrit la chaîne sous forme d'une suite d'instructions "pseudo-synchrones".
                  Si on choisit tout de même de scinder la logique en plusieurs fonctions, alors oui, il faut s'efforcer d'écrire des fonctions les plus pures possibles.
                  Du coup, passer à async/await pour des fonctions courtes ne me paraît pas si nocif que ça, tant qu'on respecte des bonnes pratiques qui datent d'avant même l'invention des promesses : complexité cyclomatique faible, nommage expressif, etc.

                  • [^] # Re: Tu tiens pas tes promesses

                    Posté par . Évalué à 2 (+0/-0).

                    Bin notre discussion portait sur le choix (ou non) de la forme async/await par rapport à la forme des promesses traditionnelles.

                    Mon commentaire parler du fait de partager des variable par rapport au fait de s'envoyer des messages.

                    Lorsqu'on choisit async/await, on a plus vraiment de fonctions, contrairement aux enchaînements de promesses, puisqu'on écrit la chaîne sous forme d'une suite d'instructions "pseudo-synchrones".

                    Je ne comprends pas. async, si j'en crois NDM, s'applique uniquement aux fonctions.

                    Mais en vrai ça ne change rien à mon propos. Si plusieurs fil d'exécution qui s’exécutent de manière asyncrhone accèdent aux même variables on est dans un état partagé, alors que si elles se transmettent des variables, on est dans du passage de message et on obtient pleins de propriétés cool. Tu gagne déjà une bonne partie de ces propriétés si tu fais « comme si ».

                    C'est suffisamment clair qu'il s'agit d'une question qui ne dépend pas de la syntaxe que tu utilise ?

                    • [^] # Re: Tu tiens pas tes promesses

                      Posté par (page perso) . Évalué à 4 (+3/-1).

                      Je ne comprends pas. async, si j'en crois NDM, s'applique uniquement aux fonctions.

                      Oui, je me suis relu et il manquait clairement quelque chose ; mon commentaire était à sous-entendre dans le contexte d'une fonction asynchrone qui comporte les promesses ou les await.

                      Dans le cas des promesses, notre fonction F va déclarer autant de fonctions (le plus souvent courtes et anonymes) qu'il y a d'étages dans la promise chain.
                      Dans le cas des async/await, le code habituellement déclaré dans les fonctions courtes et anonymes se retrouvent juste comme autant d'instructions dans la fonction F.

                      Ca ne change rien au fait que ta remarque est judicieuse (je suis fan d'Elixir et Elm donc je comprends ce que tu veux dire), même si je pense que dans le cadre d'une utilisation raisonnable d'async/await en JavaScript, ça ne pose pas de problèmes particuliers.

                      Voici le genre de code sous forme de promesses traditionnelles qu'on retrouvait fréquemment dans notre codebase au boulot :

                      function f() {
                        return asyncFunc1()
                          .then(res1 => {
                            (...) // code synchrone qui déclare une variable res2, la calcul, et qui a besoin de transmettre res1 à l'étage suivant
                            return [res1, res2];
                          })
                          .then(([res1, res2] => asyncFunc2(res1, res2))
                          .then(res3 => {
                            (...) // code synchrone qui fait un calcul depuis res3 et le retourne
                          })
                          .catch(err => {
                            console.error(err);
                          });
                      }

                      Voici ce que le même code devient avec async/await :

                      async function f() {
                        try {
                          const res1 = await asyncFunc1();
                          (...) // code synchrone qui déclare une variable res2, la calcule
                          const res3 = await asyncFunc2(res1, res2);
                          (...) // code synchrone qui fait un calcul depuis res3 et le retourne
                        catch (err) {
                          console.error(err);
                        }
                      }

                      Franchement, je préfère la deuxième version.
                      Maintenant, avec une fonction d'une complexité cyclomatique de ouf, des await noyés dans la masse, je dis pas…

        • [^] # Re: Tu tiens pas tes promesses

          Posté par . Évalué à 3 (+3/-1).

          De plus, ça force à écrire les fameuses callback dont parle l'auteur, mais avec une variante en utilisant la "arrow syntax" (qui ne propage pas le this dans les callback), qui est courte et élégante.

          En quoi utiliser des fonctions avec l'arrow syntax est plus court et élégant que ne pas en utiliser du tout ?

          Avec cette syntaxe, une autre chose que j'aime bien: on a pas besoin de déclarer ses variables (const toto) vu qu'on les récupère directement en paramètre des closures et on peut arriver à un stade où on peut considérer que déclarer des variables est une erreur de programmation

          Absolument pas. Non seulement c'est possible de ne pas déclarer de variables temporaires avec async/await, mais surtout en déclarer n'est pas une erreur de programmation et rend le code plus clair.

          Quand on écrit const xml = await res.text() ou const totozList = obj.totozes.totoz.length ? obj.totozes.totoz : [obj.totozes.totoz], il suffit de lire le nom de la variable pour savoir ce qui est retourné.

          on rajoute des embranchements et la complexité cyclomatique et donc on augmente la probabilité d'avoir des bugs (certains analyseurs statiques d'ailleurs mettent ça en valeur).

          Je ne vois absolument pas en quoi une structure de type

          r1 = f1()
          r2 = f2(r1)
          r3 = f3(r2)
          

          ajoute quoi que ce soit en complexité par rapport à f3(f2(f1())).

          Par ailleurs, avec les promesses, on ne mélange pas les scopes, et donc on ne partage pas les variables: on réduit par la même occasion les chances d'écrasement ou d'utilisation de variable accidentels de par cette isolation

          Dans l'exemple, tout est déclaré avec le mot-clé const, rien ne peut être écrasé. Par ailleurs rien n'empêche d'appliquer les bonnes pratiques comme donner des noms clairs aux variables et écrire des fonctions courtes en utilisant async/await.

          • [^] # Re: Tu tiens pas tes promesses

            Posté par (page perso) . Évalué à 2 (+1/-1).

            Dans l'exemple, tout est déclaré avec le mot-clé const, rien ne peut être écrasé.

            Malheureusement en JavaScript, c'est pas aussi simple :

            const a = [1, 2];
            
            a.push(3); // autorisé
            • [^] # Re: Tu tiens pas tes promesses

              Posté par (page perso) . Évalué à 2 (+1/-0).

              Comme dans la pluspart des langages objets, ce n'est pas parce que tu n'as pas le droit d'écraser la variable que tu n'as pas le droit d'utiliser les comportements de l'objet sous-jacent. C'est logique, ce qui pèche ici, c'est que tu utilises un objet mutable.

              • [^] # Re: Tu tiens pas tes promesses

                Posté par (page perso) . Évalué à 2 (+0/-0).

                Sauf que je ne suis pas certain que l'utilisation du mot const dans d'autre langage OO soit vraiment celle de JavaScript. Ca fait longtemps mais il me semble qu'en C++ par exemple, si on déclare un objet en const, on ne peut pas appeler de méthodes qui modifient cet objet, ou bien en tout cas que c'est une mauvaise pratique.

                • [^] # Re: Tu tiens pas tes promesses

                  Posté par (page perso) . Évalué à 2 (+1/-0).

                  En C++ peut être, je connais très mal le langage, mais dans les autres langages objet que je connaisse, le const est souvent sémantiquement identique à celui du JavaScript, quand il existe !

                  • [^] # Re: Tu tiens pas tes promesses

                    Posté par (page perso) . Évalué à 3 (+1/-0).

                    Etant donné que Java n'a pas le mot clef const, tu fais références à quels langages ? Ca interpelle ma curiosité :)

                    • [^] # Re: Tu tiens pas tes promesses

                      Posté par (page perso) . Évalué à 1 (+0/-0). Dernière modification le 30/11/18 à 17:50.

                      C#, Scala, Rust à mut/pas mut (fait plus que le const), le D (j'ai triché j'ai regardé sur Wikipedia pour celui là) sûrement d'autres par ailleurs.

                      EDIT: Java a final, je ne sais plus dans quelle mesure il peut remplacer le const (ça remonte de loin, j'ai pas fait de Java depuis 10 ans) d'ailleurs dans ce cadre il permet aussi des optims de perf sur le code compilé (on voit souvent foreach (final Type maVar in enumerable) {} - je suis un peu flou sur la syntaxe là, ça remonte à loin comme je le dis).

                      • [^] # Re: Tu tiens pas tes promesses

                        Posté par (page perso) . Évalué à 3 (+1/-0).

                        final MaClasse en Java correspond assez bien au const de JS : tu ne peux pas modifier la référence elle-même, mais si l'objet est mutable tu peux le muter (ex : tu ne peux pas écraser une liste par une autre, mais tu peux en modifier le contenu).

                        Idem avec val en Kotlin.

                        La connaissance libre : https://zestedesavoir.com

          • [^] # Re: Tu tiens pas tes promesses

            Posté par (page perso) . Évalué à 0 (+1/-2). Dernière modification le 30/11/18 à 16:39.

            En quoi utiliser des fonctions avec l'arrow syntax est plus court et élégant que ne pas en utiliser du tout ?

            Tout dépend des conventions et de l'usage, d'un point de vue syntactique pur, ça rend ce que t'écris plus court, et te permet de te focaliser sur l'essentiel. Dans tous les cas, à un moment ou un autre, tu vas écrire des fonctions, qu'elle soit nommées ou anonymes (en JS elles sont toutes des closures donc je ne distingue pas ici). Donc finalement, parfois c'est plus judicieux de l'écrire à l'endroit où tu l'utilises, et puisqu'elle a un usage unique, tu n'as pas besoin de la nommer, donc moins de surcharge cognitive.

            D'un autre côté, dans la sémantique du langage en lui même, la short arrow syntax ne se comporte pas comme les closures avec function() ou les fonction nommées, à savoir que dès lors que tu utilises une function (nommée ou non) elle dispose de son propre this, elle est un objet, elle a un état: une closure sous la forme d'une short arrow function n'a pas d'état, elle est plus proche du fonctionnel pur. D'ailleurs c'est parfois pratique car dans le contexte d'une instance, le this dans ta short arrow function va être celui de l'objet (comme le comportement des closures en PHP, et sûrement dans d'autres langages) - ça te permet de propager des comportements d'objets isolés dans du code événementiel, et dans certains cas, améliore grandement la lecture.

            Absolument pas. Non seulement c'est possible de ne pas déclarer de variables temporaires avec async/await, mais surtout en déclarer n'est pas une erreur de programmation et rend le code plus clair.

            Je n'ai pas dit que c'était une erreur dans l'absolu, mais que dans certaines conventions, ça peut être considéré comme tel. Et d'ailleurs, en ce qui concerne "rend le code plus clair" je ne suis foncièrement pas d'accord, ça relève du subjectif, mais passer par moult variable créé une surcharge cognitive, tout dépend de comment le lecteur lit bien entendu.

            ajoute quoi que ce soit en complexité par rapport à f3(f2(f1())).

            Dans l'absolu, c'est vrai, après, certains analyseurs statiques vont donner un score plus élevé à la complexité dès lors que tu rajoutes des variables temporaires. Même si sémantiquement c'est exactement la même chose, dans certains langages interprétés l'exécution n'est pas équivalente, même si le résultat est le même (par exemple, le simple fait de créer la variable intermédiaire peut être un overhead, exemple en PHP, et sûrement dans certaines VM JS aussi, sauf si par chance ton code est beaucoup exécuté et que la JIT décide de le compiler autrement). C'est sûrement pour ça que ces analyseurs le mesure d'ailleurs.

            Dans l'exemple, tout est déclaré avec le mot-clé const, rien ne peut être écrasé. Par ailleurs rien n'empêche d'appliquer les bonnes pratiques comme donner des noms clairs aux variables et écrire des fonctions courtes en utilisant async/await.

            En effet, const est une bonne pratique, d'ailleurs en Scala une des pratiques couramment admise est que rien ne devrait pas être const, et je pratique moi même cette règle quand j'écris en TypeScript. Sauf parfois dans le cas d'une variable dans un bouboucle for in ou for of, mais je conserve le for uniquement quand ça fait sens d'un point de vue performance ou sémantiquement - comme par exemple avec les NodeList en retour d'un querySelectorAll(), qui n'est ni un Array ni finalement vraiment un enumerable, sinon Array.forEach() est très bien, mais très lent, et malheureusement ne peut pas être utilisé dans certains cas.

            async/await c'est très bien, je ne suis pas contre, il ne faut pas le prendre sur ce ton ! J'espère que tu as remarqué que je ponctue énormément ce que j'écris avec "parfois", "dans certains cas", "sous certaines conventions" et j'en passe. D'ailleurs j'utilise beaucoup plus le mot-clé async, qui permet d'écrire la fonction asynchrone de façon beaucoup plus claire et beaucoup plus concise, sans avoir besoin de manipuler soit même l'objet Promise, qui rend le code extrêmement verbeux, surtout en TypeScript avec du typage fort, mais la réciproque, dans mes cas d'usage courants, n'est pas vraie, j'utilise plus souvent explicitement some_function().then().then().catch() que await.

            Après, les goûts et les couleurs, mais il ne faut pas s'énerver comme ça !

  • # Java 11

    Posté par . Évalué à 5 (+3/-0).

        public static void searchTotoz(String query) {
            HttpClient client = HttpClient.newHttpClient();
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://totoz.eu/search.xml?terms=" + query))
                    .build();
    
            client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                    .thenApply(HttpResponse::body)
                    .thenApply(SmokeTestsApplication::extract)
                    .thenAccept(System.out::println)
                    .join();
        }
    
        public static Collection<String> extractNames(String body) {
            try {
                DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                Document document = builder.parse(new ByteArrayInputStream(body.getBytes()));
    
                XPath xpath = XPathFactory.newInstance().newXPath();
                String expression = "/totozes/totoz/name/text()";
    
                XPathNodes nodes = xpath.evaluateExpression(expression, document, XPathNodes.class);
                return StreamSupport.stream(nodes.spliterator(), false).map(Node::getNodeValue).collect(Collectors.toList());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

    Complètement à l'arrache, mais c'était l'occasion d'utiliser le nouveau client http de Java 11. C'est assez verbeux (oui c'est java) et la gestion d'erreur est pourrie.

  • # Je pense que l'on tient le nouveau TapTempo

    Posté par . Évalué à 6 (+4/-0).

    Préparez vous pour une avalanche de journaux rigolos en guise de calendrier de l'avent …

  • # Go Go Go :)

    Posté par . Évalué à 3 (+2/-0).

    Hello,
    Pour moi, le Go est dans son élément ici. Les goroutines vous permettent de ne pas vous occuper de l'aspect asynchrone/synchrone.
    Go c'est une application qui tourne avec 10 à 20 Mo d'emprunte mémoire, qui se comporte très bien sur un pod k8s dont les ressources sont inférieures 100 millicore, qui démarre quasi-instantanément, dont le déploiement est simplissime un simple binaire dans un container from scratch, dont la taille sera de quelques mega (ici 6.1Mo pour binaire linux amd64).

    Le tout avec un langage qui reste simple (pour des cas simples), très bien outillé (vet, race …), génial pour la concurrence, la gestion des timeouts/fin de vie (via les context) et qui va utiliser tous les cores disponibles de façon optimale.

    Bref, qu'en pensez vous ?

    https://play.golang.org/p/ElZ99l4WTd-

    package main
    
    import (
        "encoding/xml"
        "log"
        "net/http"
    )
    
    type totozes struct {
        Name []string `xml:"totoz>name"`
    }
    
    func main() {
        res, err := http.Get("https://totoz.eu/search.xml?terms=plop")
        if err != nil {
            log.Fatal(err)
        }
    
        if expected, actual := http.StatusOK, res.StatusCode; expected != actual {
            log.Fatalf("unexpected status code [actual: %v] [expected: %v]", actual, expected)
        }
    
        var (
            d         = xml.NewDecoder(res.Body)
            totozList totozes
        )
    
        if err := d.Decode(&totozList); err != nil {
            log.Fatal(err)
        }
    
        for _, Name := range totozList.Name {
            log.Println(Name)
        }
    }
    • [^] # Re: Go Go Go :)

      Posté par . Évalué à 0 (+0/-1).

      En relisant, je m'aperçois que je n'ai pas fermé le body de la réponse. Donc voici la version corrigée :

      package main
      
      import (
          "encoding/xml"
          "log"
          "net/http"
      )
      
      type totozes struct {
          Name []string `xml:"totoz>name"`
      }
      
      func main() {
          res, err := http.Get("https://totoz.eu/search.xml?terms=plop")
          if err != nil {
              log.Fatal(err)
          }
          defer res.Body.Close()
      
          if expected, actual := http.StatusOK, res.StatusCode; expected != actual {
              log.Fatalf("unexpected status code [actual: %v] [expected: %v]", actual, expected)
          }
      
          var (
              d         = xml.NewDecoder(res.Body)
              totozList totozes
          )
      
          if err := d.Decode(&totozList); err != nil {
              log.Fatal(err)
          }
      
          for _, Name := range totozList.Name {
              log.Println(Name)
          }
      }
      • [^] # Re: Go Go Go :)

        Posté par . Évalué à 2 (+1/-1).

        Sauf erreur de ma part ton code est totalement synchrone… Comme l'objectif était de présenter les API asynchrones ça marche moins bien.

        Je trouve cette condition horrible à lire :

        if expected, actual := http.StatusOK, res.StatusCode; expected != actual {}
        • [^] # Re: Go Go Go :)

          Posté par . Évalué à 1 (+0/-0). Dernière modification le 01/12/18 à 20:12.

          C'est justement l'intérêt de Go, qui contrairement à Java par exemple, ne bloque pas les threads systèmes sur les entrées/sorties réseaux. C'est pour cela que Go propose que des clients synchrones d'un point de vue des goroutines dans son SDK.

          Je peux te dire que venant du monde Java backend, cela a toujours été l'horreur de configurer la taille des pool de threads (souvent fait au doigt mouillé) d'autant plus quand tu accèdes à des API qui répondent plus moins vite (donc bloquant plus ou moins les threads).

          J'aime le Go et son semblant d'aspect synchone car il me permet de lire le code comme un livre. C'est dire naturellement.

          • [^] # Re: Go Go Go :)

            Posté par . Évalué à 2 (+0/-0).

            C'est justement l'intérêt de Go, qui contrairement à Java par exemple, ne bloque pas les threads systèmes sur les entrées/sorties réseaux.

            Vraiment? Est-ce vrai aussi pour des bibliothèques comme netty qui se présente pourtant comme non-bloquante?

            J'étais pourtant sûr du contraire.

            • [^] # Re: Go Go Go :)

              Posté par . Évalué à 1 (+0/-0). Dernière modification le 02/12/18 à 10:47.

              Je suis complètement en accord avec toi sauf malheureusement quand les librairies n'utilisent pas Netty ou d'autres (e.g. Apache Mina). Dans mes précédents développements (je travaille pour un grand telco français), nous devions nous interconnecter avec une plateforme d'accès gérant plusieurs millions de clients. Nous utilisions un client radius en Java qui n'utilisait pas les nio. Donc, nous étions obligés de configurer nos pools de threads pour ne pas dégrader la QoS de notre plate-forme avec des pools de threads vidés car le serveur radius était par moment un peu long à répondre…

              Alors qu'en Go, l'asynchronisme (via epoll sous linux) est obligatoire car géré par le runtime de façon transparente.

              De plus, comme je l'ai déjà dit, je préfère que le code soit écrit en synchrone qu'en asynchrone car beaucoup plus simple maintenir par la suite surtout par quelqu'un d'autre.

              • [^] # Re: Go Go Go :)

                Posté par . Évalué à 2 (+0/-0).

                De plus, comme je l'ai déjà dit, je préfère que le code soit écrit en synchrone qu'en asynchrone car beaucoup plus simple maintenir par la suite surtout par quelqu'un d'autre.

                Et si tu avais 2 GET à effectuer en parallèle pour fusioner les résultats à la fin, tu aura bien un élément de syntaxe qui indique qu'on synchronise les résultats j'imagine.

                • [^] # Re: Go Go Go :)

                  Posté par . Évalué à 1 (+0/-0).

                  Bonne question merci.
                  Il faut utiliser le principe du fan-in fan-out qui permet de paralléliser N requêtes et les merger voir annuler celles qui restent si une échoue. Go montre toute sa puissance ici.

    • [^] # Re: Go Go Go :)

      Posté par . Évalué à 3 (+1/-0).

      Bref, qu'en pensez vous ?

      Beaucoup trop de pub pour un petit bout de code :)

      • [^] # Re: Go Go Go :)

        Posté par . Évalué à 1 (+0/-0).

        Oui sans aucun doute ;)
        Disons que c'est plus un partage d'expérience :p

  • # En javascript

    Posté par . Évalué à 0 (+1/-1).

    Je l'aurais plutot vu comme ca :

    const fetch = require('node-fetch')
    const xmljs = require('xml-js')
    
    const url ='https://totoz.eu/search.xml' 
    
    const searchTotoz = async query => {
        const terms = encodeURIComponent(query) 
        const xml = await fetch(`${url}?terms=${terms}`).then(r => r.text())
        const obj = xmljs.xml2js(xml,{compact: true})
        return Promise.resolve(obj.totozes.totoz.length ? obj.totozes.totoz : [obj.totozes.totoz])
    }
    
    if (!process.argv[2]) console.error("Syntax: npm start QUERY")
    else searchTotoz(process.argv[2])
            .then(tList => tList.map(t=>t.name._text).join('\n'))
            .then(console.log)
            .catch(err => console.error(err.message))

    Il ne faut pas oublier que l'utilisation de await n'empeche pas l'utilisation des blocs .then().
    D'un point de vue purement personnel, je trouve que c'est plus elegant et concis.
    Dans ce cas precis : search, then arranger l'affichage, then produire l'affichage.
    searchTotoz() devient egalement plus simple, puisqu'il n'y a plus de gestion de l'affichage ou de la sortie .

    Mais bon, il y a au moins 230 manieres differentes de le faire (full promises, full await en remplacant le .catch() par un try{} catch{}, l'utilisation du module http ou https natif de node, separer les trois responsabilitees en trois methodes distinctes, etc…).

    • [^] # Re: En javascript

      Posté par (page perso) . Évalué à 4 (+2/-0).

      return Promise.resolve(obj.totozes.totoz.length ? obj.totozes.totoz : [obj.totozes.totoz])

      Pas besoin du return Promise.resolve ici, puisque la fonction est déclarée comme async, elle retournera systématiquement une promesse qui sera résolue avec l'objet retourné.

  • # En python 3.7

    Posté par . Évalué à 2 (+0/-0). Dernière modification le 05/12/18 à 00:09.

    En utilisant la lib trio

    import trio
    import untangle
    import fire
    import asks
    
    asks.init('trio')
    
    
    async def totoz(arg):
        r = await asks.get('https://totoz.eu/search.xml',
                           params={'terms': arg})
    
        if r.status_code != 200:
            raise Exception(f'fail searchin {arg} ({r.status_code})')
    
        xml = untangle.parse(r.body.decode())
        for totoz in xml.totozes.totoz:
            print(totoz.name.cdata)
    
    
    async def asearch(*args):
        async with trio.open_nursery() as nurse:
            for arg in args:
                nurse.start_soon(totoz, arg)
    
    
    def search(*args):
        trio.run(asearch, *args)
    
    
    if __name__ == '__main__':
        fire.Fire(search)

    Pour plus d'intérêt ici, on peut passer plusieurs arguments à rechercher au script qui seront du coups exécutés parallèlement.

    Le module fire est pratique pour faire des programmes en cli.

    python totoz.py moule pas fraiche

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.