Mam prosty moduł węzła, który łączy się z bazą danych i ma kilka funkcji do odbierania danych, na przykład ta funkcja:
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);
}
});
});
}
};
Moduł zostałby nazwany w ten sposób z innego modułu węzła:
app.js:
import dbCon from './dbConnection.js';
dbCon.getUsers(console.log);
Chciałbym użyć obietnic zamiast wywołań zwrotnych, aby zwrócić dane. Do tej pory przeczytałem o zagnieżdżonych obietnicach w następującym wątku: Pisanie czystego kodu z zagnieżdżonymi obietnicami , ale nie mogłem znaleźć żadnego rozwiązania, które byłoby wystarczająco proste dla tego przypadku użycia. Jaki byłby właściwy sposób powrotu result
przy użyciu obietnicy?
Odpowiedzi:
Korzystanie z
Promise
klasyPolecam przyjrzeć się dokumentom MDN Promise, które stanowią dobry punkt wyjścia do korzystania z Promises. Alternatywnie, jestem pewien, że istnieje wiele tutoriali dostępnych online. :)
Uwaga: Nowoczesne przeglądarki obsługują już specyfikację Promises ECMAScript 6 (zobacz dokumentację MDN, do której link znajduje się powyżej) i zakładam, że chcesz użyć implementacji natywnej, bez bibliotek innych firm.
A jeśli chodzi o rzeczywisty przykład ...
Podstawowa zasada działa tak:
resolve
ireject
Może się wydawać, że to dużo, więc oto rzeczywisty przykład.
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 })
Korzystanie z funkcji języka async / await (Node.js> = 7.6)
W Node.js 7.6 kompilator JavaScript w wersji 8 został zaktualizowany z obsługą async / await . Możesz teraz zadeklarować funkcje jako istoty
async
, co oznacza, że automatycznie zwracają a,Promise
które jest rozwiązywane, gdy funkcja asynchroniczna zakończy wykonywanie. Wewnątrz tej funkcji możesz użyćawait
słowa kluczowego, aby poczekać, aż rozwiąże się kolejna obietnica.Oto przykład:
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 }
źródło
Z bluebirdem możesz użyć
Promise.promisifyAll
(iPromise.promisify
), aby dodać gotowe metody Promise do dowolnego obiektu.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') }); };
I użyj w ten sposób:
getUsersAsync().then(console.log);
lub
// 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. });
Dodawanie dystrybutorów
Bluebird obsługuje wiele funkcji, jedną z nich są dyspozytory, pozwala bezpiecznie pozbyć się połączenia po jego zakończeniu za pomocą
Promise.using
iPromise.prototype.disposer
. Oto przykład z mojej aplikacji: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(); }); }
Następnie użyj tego w ten sposób:
exports.getUsersAsync = function () { return Promise.using(getConnection()).then(function (connection) { return connection.queryAsync('SELECT * FROM Users') }); };
Spowoduje to automatyczne zakończenie połączenia, gdy obietnica zostanie rozwiązana z wartością (lub zostanie odrzucona za pomocą
Error
).źródło
try-catch
przy każdym połączeniu. Jeśli więc robisz to dość często, a Twój kod jest podobny do tego przykładowego, powinieneś to przemyśleć.Node.js w wersji 8.0.0+:
Nie musisz już używać bluebird do obiecywania metod API węzła. Ponieważ od wersji 8+ możesz używać natywnego pliku 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') }); };
Teraz nie musisz używać żadnej biblioteki innej firmy, aby wykonać obietnicę.
źródło
Zakładając, że interfejs API adaptera bazy danych nie wyświetla
Promises
się, możesz zrobić coś takiego: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(); };
Jeśli API bazy danych obsługuje,
Promises
możesz zrobić coś takiego: (tutaj widzisz moc Obietnic, twój plik wywołania zwrotnego prawie znika)exports.getUsers = function () { return connection.connect().then(function () { return connection.query('SELECT * FROM Users'); }); };
Używanie
.then()
do zwrócenia nowej (zagnieżdżonej) obietnicy.Zadzwoń z:
module.getUsers().done(function (result) { /* your code here */ });
Użyłem makiety API dla moich obietnic, twoje API może być inne. Jeśli pokażesz mi swoje API, mogę je dostosować.
źródło
Promise
konstruktora i.promise()
metodę?promise = new Promise();
?Promises
to utkniesz z używaniem wywołań zwrotnych. Po wejściu na obiecane terytorium „piramida” znika. Zobacz drugi przykład kodu, aby zobaczyć, jak to działa.2019:
Użyj tego modułu natywnego,
const {promisify} = require('util');
aby przekształcić zwykły stary wzorzec wywołania zwrotnego do wzorca obietnicy, aby uzyskać korzyści zasync/await
koduconst {promisify} = require('util'); const glob = promisify(require('glob')); app.get('/', async function (req, res) { const files = await glob('src/**/*-spec.js'); res.render('mocha-template-test', {files}); });
źródło
Konfigurując obietnicę, bierzesz dwa parametry
resolve
ireject
. W przypadku powodzenia wywołajresolve
z wynikiem, w przypadku niepowodzenia wywołajreject
z błędem.Następnie możesz napisać:
callback
zostanie wywołana z wynikiem zwróconej obietnicygetUsers
, tjresult
źródło
Na przykład przy użyciu biblioteki 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; }
źródło
Poniższy kod działa tylko dla węzła -v> 8.x
Używam tego Promisified MySQL dla Node.js.
przeczytaj ten artykuł Tworzenie oprogramowania pośredniczącego bazy danych MySQL za pomocą Node.js 8 i 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
Musisz zaktualizować węzeł -v> 8.x
musisz użyć funkcji async, aby móc używać await.
przykład:
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) }
źródło