Publié par

Il y a 5 ans -

Temps de lecture 5 minutes

Transformez votre code Node.js grâce au module de promises Bluebird

Lorsqu’on parle de promises dans l’écosystème Node.js, on pense immédiatement à la librairie Q. Toutefois, il existe de nombreux modules de promises proposant chacun des choses différentes. En particulier, le module bluebird se démarque grâce à des fonctionnalités tout à fait intéressantes telles que la “promisification”.

Promisification

Les core modules de Node.js fonctionnent à base de callback. Ainsi pour lire un fichier de façon asynchrone, il faut appeler la fonction readFile du module fs et traiter la réponse depuis le callback passé en dernier paramètre de la fonction lors de son appel :

fs.readFile "file.json", (err, val) ->
    if err
        console.error "unable to read file"
        try
            val = JSON.parse(val);
            console.log val.success
        catch e
            console.error "invalid json in file"

Bluebird permet de transformer le code précédent dans le code suivant :

fs.readFileAsync("file.json").then(JSON.parse).then (val) ->
    console.log val.success
.catch SyntaxError, (e) ->
    console.error "invalid json in file"
.catch (e) ->
    console.error "unable to read file"

promisifyAll

Cette transformation est rendue possible grâce à la promisification du module fs, via l’appel de la fonction promisifyAll qui permet de transformer toutes les fonctions exposées en fonctions renvoyant des promises :

fs = require "fs"
Promise.promisifyAll fs

fs.readFileAsync("file.js", "utf8").then(...)

Selon toute vraisemblance, les fonctions du modules sont proxifiées via un wrapping changeant la signature.
On pourra noter que le chaînage de fonctions catch sur la promise permet de différencier le traitement des erreurs en fonction de leur type. Ici, l’erreur de type SyntaxError est traitée différemment des erreurs typées autrement.

promisify

Il est également possible de ne promisifier qu’une seule fonction grâce à la fonction promisify :

redisGet = Promise.promisify(redisClient.get, redisClient)
redisGet('foo').then () ->
    #...

Il y a tout de même un piège puisque la fonction attend 2 paramètres. Le premier étant la référence de la fonction à promisifier, et le second étant l’objet auquel la fonction est rattachée.

nodeify

La fonction nodeify est également très intéressante car elle permet d’enregistrer un callback sur une promise bluebird et d’appeler celui-ci à la résolution de cette dernière :

getDataFor(input, callback) ->
    dataFromDataBase(input).nodeify(callback)

Cette possibilité est particulièrement intéressante, car elle permet de construire des API qui deviennent utilisables aussi bien par du code qui fonctionne à base de callback, qu’avec du code à base de promise.

Ainsi, si le callback est renseigné, il sera appelé. Sinon, il suffira d’exploiter la promise retournée par la fonction pour obtenir et traiter le résultat de l’appel.

Exemple exploitant le mécanisme de promise :

getDataFor("me").then (dataForMe) ->
    console.log dataForMe

Le même exemple exploitant le mécanisme de callback:

getDataFor "me", (err, dataForMe) ->
    if err
        console.error err
    console.log dataForMe

spread

En temps normal, le code suivant donnera en résultat la tableau : [1, 2, 3].

Promise.resolve([1,2,3]).nodeify (err, result) ->
    # err == null
    # result: [1,2,3]

Toutefois, l’option {spread: true} passée à l’appel de la fonction nodeify, permet de dispatcher les valeurs de résultat sur l’ensemble des arguments de la fonction de callback renseignée:

Promise.resolve([1,2,3]).nodeify (err, a, b, c) ->
    # err == null
    # a == 1
    # b == 2
    # c == 3
, {spread: true}

Last but not least, la performance

Les librairies de promesses ont souvent été décriées pour le piètre performances. L’auteur de Bluebird s’est attaché à fournir une librairie performante aussi bien au niveau de l’usage du CPU que de la mémoire. Il décrit dans un article sur Github différents extraits de code tueurs de performance:

Les chiffres parlent plus qu’une longue explication :

Ceux-ci datent du mois de mai 2014. Il peuvent donc ne plus être complètement à jour, mais permettent de se faire une idée des performances obtenues avec Bluebird et de les comparer à d’autres librairies de promesses ou bien encore d’autres techniques asynchrones.

    ├── async@0.7.0
    ├── davy@0.2.2
    ├── deferred@0.7.1
    ├── kew@0.4.0
    ├── lie@2.7.3
    ├── promise@5.0.0
    ├── q@1.0.1
    ├── rsvp@3.0.6
    ├── vow@0.4.3
    └── when@3.1.0

bench doxbee-sequential

results for 10000 parallel executions, 1 ms per I/O op

    file                                 time(ms)  memory(MB)
    promises-bluebird-generator.js            171       16.52
    callbacks-baseline.js                     197       20.68
    promises-bluebird.js                      280       26.64
    promises-lvivski-davy.js                  616       58.75
    promises-dfilatov-vow.js                  672       80.59
    promises-cujojs-when.js                   731       68.74
    callbacks-caolan-async-waterfall.js       733       44.57
    promises-calvinmetcalf-lie.js            1035      113.07
    promises-obvious-kew.js                  1047       78.50
    promises-tildeio-rsvp.js                 1121      109.49
    promises-ecmascript6-native.js           1298       96.05
    promises-then-promise.js                 1775      134.73
    promises-medikoo-deferred.js             2238      149.61
    promises-kriskowal-q.js                 19786      415.04

    Platform info:
    Windows_NT 6.1.7601 ia32
    Node.JS 0.11.13
    V8 3.25.30
    Intel(R) Core(TM) i5-2500K CPU @ 3.30GHz × 4

bench parallel (`--p 25`)

    results for 10000 parallel executions, 1 ms per I/O op

    file                                time(ms)  memory(MB)
    promises-bluebird.js                     483       63.32
    callbacks-baseline.js                    545       25.54
    promises-bluebird-generator.js           574       64.66
    promises-lvivski-davy.js                1088      128.62
    promises-cujojs-when.js                 1527      178.57
    callbacks-caolan-async-parallel.js      1635       99.87
    promises-dfilatov-vow.js                1753      196.96
    promises-then-promise.js                2553      338.36
    promises-ecmascript6-native.js          3749      309.55
    promises-obvious-kew.js                 3805      366.32
    promises-tildeio-rsvp.js                3916      462.23
    promises-calvinmetcalf-lie.js           4477      230.99
    promises-medikoo-deferred.js            4613      356.03

    Platform info:
    Windows_NT 6.1.7601 ia32
    Node.JS 0.11.13
    V8 3.25.30
    Intel(R) Core(TM) i5-2500K CPU @ 3.30GHz × 4

Le lien : https://github.com/petkaantonov/bluebird/blob/master/benchmark/stats/latest.md

Conclusion

La librairie bluebird est riche en fonctions pour le moins intéressantes, vous pouvez les retrouver sur la page de documentation du projet GitHub :

Lien : https://github.com/petkaantonov/bluebird/blob/master/API.md

Si vous n’utilisez pas les promesses ou n’êtes pas encore tout à fait convaincu de leur intérêt, vous pouvez en lire plus au sujet des promesses dans l’article suivant : http://spion.github.io/posts/why-i-am-switching-to-promises.html

Publié par

Publié par Alexis Kinsella

Alexis Kinsella est un consultant Xebia passionné aussi bien par les problématiques frontend (web et mobile) que backend. Après de longues années passées sur les technologies Java, Alexis a fait d'iOS, Node.js et du Cloud ( AWS Certified Solutions Architect - Associate ) ses nouveaux terrains d'expérimentation lui permettant d'explorer les architectures web et mobiles ainsi que leurs problématiques associées.

Commentaire

0 réponses pour " Transformez votre code Node.js grâce au module de promises Bluebird "

  1. Publié par , Il y a 4 ans

    Bon article, je cherchais justement de la doc sur ce module. Thanks !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.