Замена обратных вызовов с помощью promises в Node.js
У меня есть простой модуль node, который подключается к базе данных и имеет несколько функций для приема данных, например, эту функцию:
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);
}
});
});
}
};
Модуль будет вызван таким образом из другого модуля node:
app.js:
import dbCon from './dbConnection.js';
dbCon.getUsers(console.log);
Я хотел бы использовать promises вместо обратных вызовов, чтобы вернуть данные.
До сих пор я читал о вложенных promises в следующем потоке: Написание чистого кода с вложенным Promises, но я не мог найти какое-либо решение, которое просто достаточно для этого варианта использования.
Каким будет правильный способ вернуть result
с помощью обещания?
Ответы
Ответ 1
Использование класса Promise
Я рекомендую взглянуть на MDN Promise docs, которые предлагают хорошую отправную точку для использования Promises. Кроме того, я уверен, что в Интернете доступно много учебников.:)
Примечание. Современные браузеры уже поддерживают спецификацию ECMAScript 6 Promises (см. документы MDN, приведенные выше), и я предполагаю, что вы хотите использовать собственную реализацию без сторонних библиотек. p >
Что касается фактического примера...
Основной принцип работает следующим образом:
- Ваш API называется
- Вы создаете новый объект Promise, этот объект принимает одну функцию как параметр конструктора
- Ваша предоставленная функция вызывается базовой реализацией, а функции задаются две функции -
resolve
и reject
- Как только вы выполните свою логику, вы вызываете одну из них, чтобы либо заполнить обещание, либо отклонить его с ошибкой.
Это может показаться много, поэтому здесь приведен пример.
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 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
})
Использование функции языка асинхронного/ожидающего (Node.js >= 7.6)
В Node.js 7.6 компилятор v8 JavaScript был обновлен с помощью поддержки async/await. Теперь вы можете объявлять функции как async
, что означает, что они автоматически возвращают Promise
, который разрешается, когда функция async завершает выполнение. Внутри этой функции вы можете использовать ключевое слово await
, чтобы подождать, пока не решится другое обещание.
Вот пример:
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
}
Ответ 2
С bluebird вы можете использовать Promise.promisifyAll
(и Promise.promisify
), чтобы добавить готовые методы Promise к любому объекту.
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')
});
};
И используйте вот так:
getUsersAsync().then(console.log);
или
// 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.
});
Добавление устройств удаления
Bluebird поддерживает множество функций, один из которых - утилиты, он позволяет безопасно удалять соединение после его завершения с помощью Promise.using
и Promise.prototype.disposer
. Вот пример из моего приложения:
function getConnection(host, user, password, port) {
// connection was already promisified at this point
// The object literal syntax is ES6, it 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();
});
}
Затем используйте его следующим образом:
exports.getUsersAsync = function () {
return Promise.using(getConnection()).then(function (connection) {
return connection.queryAsync('SELECT * FROM Users')
});
};
Это автоматически закончит соединение после того, как обещание будет устранено со значением (или отклоняется с помощью Error
).
Ответ 3
Node.js версия 8.0. 0+:
Вы не должны использовать Bluebird больше promisify методы узла API. Потому что, начиная с версии 8+ вы можете использовать нативный 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')
});
};
Теперь не нужно использовать какую-либо стороннюю библиотеку для выполнения обещания.
Ответ 4
Предполагая, что API-интерфейс адаптера базы данных не выводит Promises
, вы можете сделать что-то вроде:
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();
};
Если API базы данных поддерживает Promises
, вы можете сделать что-то вроде: (здесь вы видите мощность Promises, ваш обратный вызов практически исчезает)
exports.getUsers = function () {
return connection.connect().then(function () {
return connection.query('SELECT * FROM Users');
});
};
Использование .then()
для возврата нового (вложенного) обещания.
Позвонить с помощью:
module.getUsers().done(function (result) { /* your code here */ });
Я использовал mockup API для своего Promises, ваш API мог бы быть другим. Если вы покажете мне свой API, я смогу его адаптировать.
Ответ 5
При настройке обещания вы берете два параметра: resolve
и reject
. В случае успеха вызовите resolve
с результатом, в случае вызова отказа reject
с ошибкой.
Затем вы можете написать:
getUsers().then(callback)
callback
будет вызван с результатом обещания, возвращенного из getUsers
, то есть result
Ответ 6
Использование библиотеки Q, например:
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;
}
Ответ 7
Код ниже работает только для узла -v> 8.x
Я использую это промежуточное ПО Promisified MySQL для Node.js
Прочтите эту статью. Создайте промежуточное ПО базы данных MySQL с Node.js 8 и 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
Вы должны обновить узел -v> 8.x
Вы должны использовать асинхронную функцию, чтобы иметь возможность использовать await.
пример:
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)
}
Ответ 8
2019:
Используйте этот собственный модуль const {promisify} = require('util');
преобразовать простой старый шаблон обратного вызова в шаблон обещания, чтобы вы могли получить пользу от кода 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});
});