javascript - Reemplazo de devoluciones de llamada con promesas en Node.js
promise q (8)
Tengo un módulo de nodo simple que se conecta a una base de datos y tiene varias funciones para recibir datos, por ejemplo, esta función:
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);
}
});
});
}
};
El módulo se llamaría de esta manera desde un módulo de nodo diferente:
app.js:
import dbCon from ''./dbConnection.js'';
dbCon.getUsers(console.log);
Me gustaría usar promesas en lugar de devoluciones de llamada para devolver los datos.
Hasta ahora he leído sobre promesas anidadas en el siguiente hilo:
Escribir código limpio con promesas anidadas
, pero no pude encontrar ninguna solución que sea lo suficientemente simple para este caso de uso.
¿Cuál sería la forma correcta de devolver el
result
utilizando una promesa?
2019:
Utilice ese módulo nativo
const {promisify} = require(''util'');
para convertir un patrón de devolución de llamada antiguo simple en un patrón de promesa para que pueda obtener beneficios del código
async/await
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});
});
Usando la clase
Promise
Recomiendo echar un vistazo a los documentos Promise de MDN que ofrecen un buen punto de partida para usar Promises. Alternativamente, estoy seguro de que hay muchos tutoriales disponibles en línea. :)
Nota: los navegadores modernos ya son compatibles con la especificación de Promesas ECMAScript 6 (consulte los documentos de MDN vinculados anteriormente) y supongo que desea utilizar la implementación nativa, sin bibliotecas de terceros.
En cuanto a un ejemplo real ...
El principio básico funciona así:
- Tu API se llama
- Crea un nuevo objeto Promise, este objeto toma una sola función como parámetro constructor
-
La implementación subyacente llama a su función proporcionada y le da dos funciones:
resolve
yreject
- Una vez que haga su lógica, llame a uno de estos para completar la Promesa o rechazarla con un error
Esto puede parecer mucho, así que aquí hay un ejemplo real.
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
})
Uso de la función de lenguaje asíncrono / espera (Node.js> = 7.6)
En Node.js 7.6, el compilador de JavaScript v8 se actualizó con
soporte asíncrono / en espera
.
Ahora puede declarar las funciones como
async
, lo que significa que devuelven automáticamente una
Promise
que se resuelve cuando la función asíncrona completa la ejecución.
Dentro de esta función, puede usar la palabra clave wait para esperar hasta que se resuelva otra Promesa.
Aquí hay un ejemplo:
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
}
Al configurar una promesa, toma dos parámetros,
resolve
y
reject
.
En caso de éxito, la llamada se
resolve
con el resultado, en caso de falla, la llamada se
reject
con el error.
Entonces puedes escribir:
getUsers().then(callback)
se devolverá la
callback
llamada con el resultado de la promesa devuelta por
getUsers
, es decir, el
result
Con
bluebird
puede usar
Promise.promisifyAll
(y
Promise.promisify
) para agregar métodos preparados para Promise a cualquier objeto.
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'')
});
};
Y use así:
getUsersAsync().then(console.log);
o
// 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.
});
Agregar trituradores
Bluebird admite muchas funciones, una de ellas es la de los eliminadores, le permite deshacerse de una conexión de forma segura después de que
Promise.using
con la ayuda de
Promise.using
y
Promise.prototype.disposer
.
Aquí hay un ejemplo de mi aplicación:
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();
});
}
Entonces úsalo así:
exports.getUsersAsync = function () {
return Promise.using(getConnection()).then(function (connection) {
return connection.queryAsync(''SELECT * FROM Users'')
});
};
Esto finalizará automáticamente la conexión una vez que la promesa se resuelva con el valor (o se rechace con un
Error
).
El siguiente código solo funciona para el nodo -v> 8.x
Uso este middleware Promisified MySQL para Node.js
lea este artículo Cree un middleware de base de datos MySQL con Node.js 8 y 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
Debe actualizar el nodo -v> 8.x
debe usar la función asíncrona para poder usar wait.
ejemplo:
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)
}
Suponiendo que la API de su adaptador de base de datos no genera
Promises
sí mismo, puede hacer algo como:
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 la API de la base de datos admite
Promises
, podría hacer algo como esto: (aquí puede ver el poder de Promesas, su
pelusa de devolución de llamada
prácticamente desaparece)
exports.getUsers = function () {
return connection.connect().then(function () {
return connection.query(''SELECT * FROM Users'');
});
};
Usando
.then()
para devolver una nueva promesa (anidada).
Llamar con:
module.getUsers().done(function (result) { /* your code here */ });
Utilicé una API de maqueta para mis Promesas, su API podría ser diferente. Si me muestras tu API, puedo adaptarla.
Usando la biblioteca Q por ejemplo:
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;
}
Node.js versión 8.0.0+:
Ya no tiene que usar bluebird para promisificar los métodos API del nodo. Porque, a partir de la versión 8+, puede usar util.promisify nativo:
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'')
});
};
Ahora, no tiene que usar ninguna biblioteca de terceros para hacer la promesa.