web-dev-qa-db-fra.com

Remplacement des rappels par des promesses dans Node.js

J'ai un module de nœud simple qui se connecte à une base de données et a plusieurs fonctions pour recevoir des données, par exemple cette fonction:


dbConnection.js:

import mysql from 'mysql';

const connection = mysql.createConnection({
  Host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'db'
});

export default {
  getUsers(callback) {
    connection.connect(() => {
      connection.query('SELECT * FROM Users', (err, result) => {
        if (!err){
          callback(result);
        }
      });
    });
  }
};

Le module serait appelé ainsi à partir d'un module de nœud différent:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

J'aimerais utiliser des promesses au lieu des rappels pour renvoyer les données . Jusqu'à présent, j'ai lu sur les promesses imbriquées dans le fil suivant: Écriture de code propre avec des promesses imbriquées , toute solution assez simple pour ce cas d'utilisation .. Quel serait le bon moyen de retourner result en utilisant une promesse?

85
Lior Erez

Utilisation de la classe Promise

Je vous recommande de consulter les documents Promise de MDN qui constituent un bon point de départ pour utiliser Promises. Alternativement, je suis sûr qu'il existe de nombreux tutoriels disponibles en ligne. :)

Remarque: Les navigateurs modernes prennent déjà en charge la spécification ECMAScript 6 de Promises (voir la documentation MDN liée ci-dessus) et je suppose que vous souhaitez utiliser l'implémentation native, sans bibliothèques tierces.

En ce qui concerne un exemple réel ...

Le principe de base fonctionne comme ceci:

  1. Votre API s'appelle
  2. Vous créez un nouvel objet Promise, cet objet prend une seule fonction en tant que paramètre constructeur
  3. Votre fonction fournie est appelée par l’implémentation sous-jacente et deux fonctions sont attribuées à la fonction - resolve et reject.
  4. Une fois que vous avez suivi votre logique, vous appelez l’une de ces personnes pour remplir la promesse ou la rejeter avec une erreur.

Cela peut sembler beaucoup alors voici un exemple réel.

exports.getUsers = function getUsers () {
  // Return the Promise right away, unless you really need to
  // do something before you create a new Promise, but usually
  // this can go into the function below
  return new Promise((resolve, reject) => {
    // reject and resolve are functions provided by the Promise
    // implementation. Call only one of them.

    // Do your logic here - you can do WTF you want.:)
    connection.query('SELECT * FROM Users', (err, result) => {
      // PS. Fail fast! Handle errors first, then move to the
      // important stuff (that's a good practice at least)
      if (err) {
        // Reject the Promise with an error
        return reject(err)
      }

      // Resolve (or fulfill) the promise with data
      return resolve(result)
    })
  })
}

// Usage:
exports.getUsers()  // Returns a Promise!
  .then(users => {
    // Do stuff with users
  })
  .catch(err => {
    // handle errors
  })

Utilisation de la fonctionnalité async/wait language (Node.js> = 7.6)

Dans Node.js 7.6, le compilateur JavaScript v8 a été mis à niveau avec async/wait support . Vous pouvez maintenant déclarer des fonctions comme étant async, ce qui signifie qu'elles renvoient automatiquement une Promise qui est résolue lorsque la fonction asynchrone termine son exécution. Dans cette fonction, vous pouvez utiliser le mot clé await pour attendre la résolution d'une autre promesse.

Voici un exemple:

exports.getUsers = async function getUsers() {
  // We are in an async function - this will return Promise
  // no matter what.

  // We can interact with other functions which return a
  // Promise very easily:
  const result = await connection.query('select * from users')

  // Interacting with callback-based APIs is a bit more
  // complicated but still very easy:
  const result2 = await new Promise((resolve, reject) => {
    connection.query('select * from users', (err, res) => {
      return void err ? reject(err) : resolve(res)
    })
  })
  // Returning a value will cause the promise to be resolved
  // with that value
  return result
}
90
Robert Rossmann

Avec bluebird, vous pouvez utiliser Promise.promisifyAll (et Promise.promisify ) pour ajouter des méthodes Promise ready à tout objet.

var Promise = require('bluebird');
// Somewhere around here, the following line is called
Promise.promisifyAll(connection);

exports.getUsersAsync = function () {
    return connection.connectAsync()
        .then(function () {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

Et utilisez comme ceci:

getUsersAsync().then(console.log);

ou

// Spread because MySQL queries actually return two resulting arguments, 
// which Bluebird resolves as an array.
getUsersAsync().spread(function(rows, fields) {
    // Do whatever you want with either rows or fields.
});

Ajout de broyeurs

Bluebird prend en charge de nombreuses fonctionnalités, parmi lesquelles les dispositifs de suppression, elle vous permet de disposer en toute sécurité d'une connexion après l'avoir terminée à l'aide de Promise.using et Promise.prototype.disposer. Voici un exemple de mon application:

function getConnection(Host, user, password, port) {
    // connection was already promisified at this point

    // The object literal syntax is ES6, it's the equivalent of
    // {Host: Host, user: user, ... }
    var connection = mysql.createConnection({Host, user, password, port});
    return connection.connectAsync()
        // connect callback doesn't have arguments. return connection.
        .return(connection) 
        .disposer(function(connection, promise) { 
            //Disposer is used when Promise.using is finished.
            connection.end();
        });
}

Ensuite, utilisez-le comme ceci:

exports.getUsersAsync = function () {
    return Promise.using(getConnection()).then(function (connection) {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

Cela mettra automatiquement fin à la connexion une fois que la promesse aura résolu avec la valeur (ou rejetera avec une Error).

31
Madara Uchiha

Node.js version 8.0.0+:

Vous n'êtes plus obligé d'utiliser bluebird pour promisifier les méthodes de l'API de nœud. Parce qu'à partir de la version 8, vous pouvez utiliser natif util.promisify :

const util = require('util');

const connectAsync = util.promisify(connection.connectAsync);
const queryAsync = util.promisify(connection.queryAsync);

exports.getUsersAsync = function () {
    return connectAsync()
        .then(function () {
            return queryAsync('SELECT * FROM Users')
        });
};

Maintenant, n'utilisez aucune bibliothèque tierce pour faire la promisify. 

9
asmmahmud

En supposant que votre API d’adaptateur de base de données ne génère pas Promises elle-même, vous pouvez effectuer les opérations suivantes:

exports.getUsers = function () {
    var promise;
    promise = new Promise();
    connection.connect(function () {
        connection.query('SELECT * FROM Users', function (err, result) {
            if(!err){
                promise.resolve(result);
            } else {
                promise.reject(err);
            }
        });
    });
    return promise.promise();
};

Si l'API de base de données prend en charge Promises, vous pourriez faire quelque chose comme: (ici, vous voyez le pouvoir de Promises, votre fluff callback disparaît à peu près)

exports.getUsers = function () {
    return connection.connect().then(function () {
        return connection.query('SELECT * FROM Users');
    });
};

Utiliser .then() pour renvoyer une nouvelle promesse (imbriquée).

Appeler avec:

module.getUsers().done(function (result) { /* your code here */ });

J'ai utilisé une API de maquette pour mes promesses, votre API pourrait être différente. Si vous me montrez votre API, je peux l’adapter.

3
Halcyon

Lors de la création d'une promesse, vous prenez deux paramètres, resolve et reject. En cas de succès, appelez resolve avec le résultat, en cas d'échec, appelez reject avec l'erreur.

Ensuite, vous pouvez écrire:

getUsers().then(callback)

callback sera appelé avec le résultat de la promesse renvoyée de getUsers, c.-à-d. result

2
Tom

En utilisant la bibliothèque Q par exemple:

function getUsers(param){
    var d = Q.defer();

    connection.connect(function () {
    connection.query('SELECT * FROM Users', function (err, result) {
        if(!err){
            d.resolve(result);
        }
    });
    });
    return d.promise;   
}
2
satchcoder

2019:

Utilisez ce module natif const {promisify} = require('util'); pour convertir un ancien modèle de rappel en un modèle prometteur vous permettant d'obtenir un bénéfice de async/await code

const {promisify} = require('util');
const glob = promisify(require('glob'));

app.get('/', async function (req, res) {
    var glob = promisify(require('glob'));
    const files = await glob('src/**/*-spec.js');
    res.render('mocha-template-test', {files});
});

2
pery mimon

Le code ci-dessous ne fonctionne que pour le noeud -v> 8.x

J'utilise ce Middleware MySQL promisifié pour Node.js

lire cet article Créer un middleware de base de données MySQL avec Node.js 8 et Async/Await

database.js

var mysql = require('mysql'); 

// node -v must > 8.x 
var util = require('util');


//  !!!!! for node version < 8.x only  !!!!!
// npm install util.promisify
//require('util.promisify').shim();
// -v < 8.x  has problem with async await so upgrade -v to v9.6.1 for this to work. 



// connection pool https://github.com/mysqljs/mysql   [1]
var pool = mysql.createPool({
  connectionLimit : process.env.mysql_connection_pool_Limit, // default:10
  Host     : process.env.mysql_Host,
  user     : process.env.mysql_user,
  password : process.env.mysql_password,
  database : process.env.mysql_database
})


// Ping database to check for common exception errors.
pool.getConnection((err, connection) => {
if (err) {
    if (err.code === 'PROTOCOL_CONNECTION_LOST') {
        console.error('Database connection was closed.')
    }
    if (err.code === 'ER_CON_COUNT_ERROR') {
        console.error('Database has too many connections.')
    }
    if (err.code === 'ECONNREFUSED') {
        console.error('Database connection was refused.')
    }
}

if (connection) connection.release()

 return
 })

// Promisify for Node.js async/await.
 pool.query = util.promisify(pool.query)



 module.exports = pool

Vous devez mettre à niveau le noeud -v> 8.x 

vous devez utiliser la fonction asynchrone pour pouvoir utiliser wait.

exemple:

   var pool = require('./database')

  // node -v must > 8.x, --> async / await  
  router.get('/:template', async function(req, res, next) 
  {
      ...
    try {
         var _sql_rest_url = 'SELECT * FROM arcgis_viewer.rest_url WHERE id='+ _url_id;
         var rows = await pool.query(_sql_rest_url)

         _url  = rows[0].rest_url // first record, property name is 'rest_url'
         if (_center_lat   == null) {_center_lat = rows[0].center_lat  }
         if (_center_long  == null) {_center_long= rows[0].center_long }
         if (_center_zoom  == null) {_center_zoom= rows[0].center_zoom }          
         _place = rows[0].place


       } catch(err) {
                        throw new Error(err)
       }
0
hoogw