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?
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:
- votre API s'appelle
- vous créez un nouvel objet Promise, cet objet prend une seule fonction comme paramètre de constructeur
- votre fonction fournie est appelée par l'implémentation sous-jacente et la fonction est donnée deux fonctions -
resolve
etreject
- 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
}
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
).
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.
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.
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
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;
}
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)
}