Remplacer les callbacks par des promesses dans Node.js

j'ai un module de noeud 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é de cette façon à partir d'un module de noeud différent:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

je voudrais utiliser promesses au lieu de rappels afin de 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 , mais je n'ai pas pu trouver une solution qui est assez simple pour ce cas d'utilisation. Quelle serait la bonne façon de retourner result en utilisant une promesse?

83
demandé sur Lior Erez 2015-02-10 16:03:51

7 réponses

utilisant la classe Promise classe

je vous recommande de jeter un oeil à MDN's Promise docs qui offrent un bon point de départ pour l'utilisation des promesses. Alternativement, je suis sûr qu'il y a beaucoup de tutoriels disponibles en ligne.:)

Note: les navigateurs modernes prennent déjà en charge la spécification ECMAScript 6 des promesses (voir les docs MDN liés ci-dessus) et je suppose que vous voulez utiliser le natif mise en œuvre, sans bibliothèques tierces.

comme pour 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 comme paramètre de constructeur
  3. votre fonction fournie est appelée par l'implémentation sous-jacente et la fonction est donnée deux fonctions - resolve et reject
  4. une fois que vous avez fait votre logique, vous appelez l'un d'eux pour soit remplir la promesse ou la rejeter avec une erreur

Cela peut sembler beaucoup, donc 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
  })

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

Dans Le Nœud.js 7.6, le compilateur JavaScript v8 a été mis à jour avec async/en attente de support . Vous pouvez maintenant déclarer les fonctions comme étant async , ce qui signifie qu'elles renvoient automatiquement un Promise qui est résolu lorsque la fonction async termine l'exécution. Dans cette fonction, vous pouvez utiliser le mot-clé await pour attendre qu'une autre promesse se résolve.

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
}
87
répondu Robert Rossmann 2017-08-15 14:42:44

avec bluebird vous pouvez utiliser Promise.promisifyAll (et Promise.promisify ) pour ajouter des méthodes prêtes à promettre à n'importe quel 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 utiliser 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 d'éliminateurs

Bluebird supporte beaucoup de fonctionnalités, l'un d'eux est éliminateurs, il vous permet de disposer d'une connexion après avoir terminé avec 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();
        });
}

alors 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 se résout avec la valeur (ou rejette avec un Error ).

30
répondu Madara Uchiha 2016-01-11 09:42:00

Node.js version 8.0.0+:

vous ne devez plus utiliser bluebird pour promisifier les méthodes API de noeud. Parce que, à partir de la version 8+, Vous pouvez utiliser l'util natif .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, ne pas avoir à utiliser une lib tierce partie pour faire le promisify.

6
répondu asmmahmud 2017-11-14 23:35:49

en supposant que votre API d'adaptateur de base de données ne produit pas Promises lui-même, vous pouvez faire quelque chose comme:

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 pouvez faire quelque chose comme: (ici, vous voyez le pouvoir des promesses, votre flou de rappel disparaît assez facilement)

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

utilisant .then() pour retourner une nouvelle promesse (imbriquée).

Appel:

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
répondu Halcyon 2015-02-10 13:49:47

lors de la mise en place d'une promesse, vous prenez deux paramètres, resolve et reject . En cas de succès, appeler resolve avec le résultat, en cas d'échec appeler reject avec l'erreur.

alors vous pouvez écrire:

getUsers().then(callback)

callback sera appelé avec le résultat de la promesse retournée de getUsers , i.e. result

2
répondu Tom 2015-02-10 13:08:23

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
répondu satchcoder 2015-09-23 15:28:21

ci-dessous le code fonctionne seulement pour le noeud-v > 8.x

j'utilise ce promisified MySQL middleware for Node.js

lire cet article Créer un Middleware de Base de données MySQL avec le Noeud.js 8 et Async / attente

de la base de données.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 à jour node-v > 8.x

vous devez utiliser la fonction async pour pouvoir utiliser l'attente.

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
répondu hoogw 2018-06-01 21:19:24