Замена обратных вызовов обещаниями в узле.js
у меня простой модуль, который подключается к базе данных и имеет несколько функций для получения данных, например эта функция:
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);
}
});
});
}
};
модуль будет вызван таким образом из другого модуля узла:
приложение.js:
import dbCon from './dbConnection.js';
dbCon.getUsers(console.log);
Я хотел бы использовать обещания вместо обратных вызовов, чтобы вернуть данные.
До сих пор я читал о вложенных обещания следующий поток: написание чистого кода с вложенными обещаниями, но я не смог найти достаточно простого решения для этого случая использования.
Какой был бы правильный способ вернуть result
используя свое обещание?
7 ответов:
С помощью
Promise
классрекомендую взглянуть на обещание MDN docs, которые предлагают хорошую отправную точку для использования обещаниями. Кроме того, я уверен, что есть много учебников доступны в интернете.:)
Примечание: современные браузеры уже поддерживают спецификацию ECMAScript 6 обещаний (см. документы MDN, связанные выше), и я предполагаю, что вы хотите использовать собственную реализацию без третьей стороны библиотеки.
как для фактического примера...
основной принцип работает следующим образом:
- ваш 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'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 })
использование функции языка async/await (узел.js >=7.6)
В Узел.js 7.6, компилятор JavaScript v8 был обновлен с асинхронный/ждут поддержки. Теперь вы можете объявить функции как
async
, что означает, что они автоматически возвращаютPromise
который решается, когда асинхронная функция завершает выполнение. Внутри этой функции, вы можете использовать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 }
С птица можно использовать
Promise.promisifyAll
(иPromise.promisify
), чтобы добавить обещание готовые методы любого объекта.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 поддерживает множество функций, одна из них-disposers, она позволяет безопасно утилизировать соединение после его окончания с помощью
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'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(); }); }
тогда используйте его так:
exports.getUsersAsync = function () { return Promise.using(getConnection()).then(function (connection) { return connection.queryAsync('SELECT * FROM Users') }); };
это автоматически завершит соединение, как только обещание разрешится со значением (или отклонит с
Error
).
узел.js версия 8.0.0+:
вы не должны использовать птица чтобы больше обещать методы API узла. Потому что, начиная с версии 8+ вы можете использовать родной util.обещай:
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') }); };
теперь, не нужно использовать любую стороннюю lib, чтобы сделать promisify.
предполагая, что ваш 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
вы могли бы сделать что-то вроде: (здесь вы видите силу обещаний, ваш обратный вызов пух в значительной степени исчезает)exports.getUsers = function () { return connection.connect().then(function () { return connection.query('SELECT * FROM Users'); }); };
используя
.then()
чтобы вернуть новое (вложенное) обещание.звонок с:
module.getUsers().done(function (result) { /* your code here */ });
я использовал макет API для моих обещаний, ваш API может быть отличающийся. Если вы покажете мне свой API, я могу его адаптировать.
при настройке обещания вы берете два параметра,
resolve
иreject
. В случае успеха звонитеresolve
с результатом, в случае отказа вызовитеreject
с ошибкой.тогда вы можете написать:
getUsers().then(callback)
callback
будет вызван с результатом обещания, возвращенного отgetUsers
, т. е.result
используя библиотеку 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; }
ниже код работает только для узла-v > 8.x
Я использую этот Promisified MySQL middleware для узла.js
прочитать эту статью создайте промежуточное программное обеспечение базы данных MySQL с узлом.js 8 и Async / Await