У меня проблема с проектированием связи с базой данных MySQL в моем приложении Nodejs. Самая большая проблема заключается в том, что запросы являются асинхронными, поэтому мне становится сложно разрабатывать мои проекты. Например, у меня есть excercises.js
УПРАЖНЕНИЯ.JS
var express = require('express');
var database = require('../database/database.js');
var router = express.Router();
console.info(database.db(saveDbData))
/* GET users listing. */
router.get('/', function(req, res, next) {
res.render('exercises',{title: 'Exercises', ex: #DATABASE RESULT});
});
module.exports = router;
При необходимости записать результат запроса в поле бывший.
Затем я пишу модуль для обработки соединения mysql.
БАЗА ДАННЫХ.JS
var pool = mysql.createPool({
connectionLimit: 10000,
host: 'localhost',
user: 'root',
password: 'password',
database: 'Example'
});
var results;
var db = function(){
pool.query('SELECT name FROM Exercises', function(error, results, fields){
if (error) throw error;
res = results;
})
return res;
}
module.exports = {
db: db,
}
Очевидно, это не работает, потому что pool.query асинхронный. Единственная альтернатива, которую я нашел в Интернете, выглядит примерно так:
УПРАЖНЕНИЯ.JS
var pool = mysql.createPool({
connectionLimit: 10000,
host: 'localhost',
user: 'root',
password: 'password',
database: 'Example'
});
pool.query('SELECT name FROM Exercises', function(error, results, fields){
if (error) throw error;
router.get('/', function(req, res, next) {
res.render('exercises',{title: 'Exercises', ex: result[0].name});
});
})
Но таким образом части mysql и части маршрутизации/рендеринга смешиваются. Это все еще хорошо продуманное решение? Есть ли более изящные решения?
РЕДАКТИРОВАТЬ:
Я изменил файлы и использовал Promise следующим образом.
УПРАЖНЕНИЯ.JS
var express = require('express');
var database = require('../database/database.js');
var router = express.Router();
var data = database.db()
.then(
function(data){
console.info("Resolved");
/* GET users listing. */
router.get('/', function(req, res, next) {
res.render('exercises',{title: 'Exercises', ex: data[0].name});
});
})
.catch(
error => console.error(error));
module.exports = router;
БАЗА ДАННЫХ.JS
ar mysql = require('mysql');
var pool = mysql.createPool({
connectionLimit: 10000,
host: 'localhost',
user: 'root',
password: 'password',
database: 'Example'
});
var res;
var db = function(){
return new Promise(function(resolve, reject){
pool.query('SELECT name FROM Exercises', function(error, results, fields){
if (error) reject(error);
resolve(results)
})
})
}
module.exports = {
db: db,
}
И это работает, но я не думаю, что это лучшее решение. Например, что, если я хочу получить данные для рендеринга из большего количества запросов? Я новичок в этих технологиях, поэтому я не могу найти лучший способ интеграции базы данных и рендеринга html-страниц.
Используйте асинхронный/ожидающий синтаксис
Я рекомендую вам использовать сиквел или другой ORM, это делает всю работу с БД намного чище.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете использовать модули npm для выполнения асинхронной задачи с MySQL. Я рекомендовал выбрать секвенировать или jm-эз-mysql. Если вы выберете jm-эз-mysql, тогда ваша структура кода будет выглядеть так:
сервер.js
require('./config/database.js');
./config/база данных.js
const sql = require('jm-ez-mysql');
// Init DB Connection
const connection = My.init({
host: process.env.DBHOST,
user: process.env.DBUSER,
password: process.env.DBPASSWORD,
database: process.env.DATABASE,
dateStrings: true,
charset: 'utf8mb4',
timezone: 'utc',
multipleStatements: true,
});
module.exports = {
connection,
};
После этого вы можете использовать MySQL асинхронно.
./упражнение.js
const sql = require('jm-ez-mysql');
const exerciseUtil = {};
exerciseUtil.searchUserById = async (id) => {
try {
// table name, db fields, condition, values
const result = await sql.first('users', ['id, name, email'], 'id = ?', [id]);
return result; // Which return your result in object {}
} catch (err) {
console.info(err);
throw err;
}
};
module.exports = exerciseUtil;
Я надеюсь, что это помогает. Удачного кодирования :)
Вы когда-нибудь задумывались, почему все веб-фреймворки в узле требуют, чтобы вы возвращали ответы, используя объект res, а не просто return? Это потому, что все веб-фреймворки ожидают, что вам нужно сделать что-то асинхронное.
Рассмотрим дизайн веб-фреймворка, похожий на Laravel (PHP) или Spring Framework (Java):
// Theoretical synchronous framework API:
app.get('/path', function (request) {
return "<html><body> Hello World </body></html>";
});
Затем, если вам нужно сделать что-то асинхронное, вы столкнетесь с проблемой, что данные, которые вы извлекаете, не возвращаются к тому времени, когда вам нужно вернуть HTTP-запрос:
// Theoretical synchronous framework API:
app.get('/path', function (request) {
return ??? // OH NO! I need to return now!!
});
Именно по этой причине веб-фреймворки в javascript не действуют на возвращаемые значения. Вместо этого он передает вам обратный вызов, когда вы закончите:
// Express.js
app.get('/path', function (request, response) {
doSomethingAsync((err, result) => {
response.send(result);
});
});
Итак, для вашего кода вам просто нужно сделать:
router.get('/', function(req, res) {
pool.query('SELECT name FROM Exercises', function(error, results, fields){
if (error) throw error;
res.render('exercises',{title: 'Exercises', ex: result[0].name});
});
});
Экспортировать базу данных так же просто, как экспортировать pool:
db.js
var pool = mysql.createPool({
connectionLimit: 10000,
host: 'localhost',
user: 'root',
password: 'password',
database: 'Example'
});
module.exports = {
pool: pool
}
упражнения.js
let db = require('./db');
// you can now use db.pool in the rest of your code
// ..
Вместо того, чтобы кодировать операторы SELECT в ваших контроллерах (маршрутах), вы можете (и должны) кодировать их в своих модулях db:
db.js
var pool = mysql.createPool({
connectionLimit: 10000,
host: 'localhost',
user: 'root',
password: 'password',
database: 'Example'
});
function getExerciseNames (callback) {
pool.query('SELECT name FROM Exercises',callback);
}
module.exports = {
pool: pool
}
Затем в логике вашего контроллера вам просто нужно сделать:
router.get('/', function(req, res) {
db.getExerciseNames(function(error, results, fields){
if (error) throw error;
res.render('exercises',{
title: 'Exercises',
ex: result[0].name
});
});
});
Если вы намерены запросить в базе данных только однажды для кэширования значения упражнений, не инвертируйте поток экспресс-маршрутизации. Вместо этого реализуйте кэширование на уровне базы данных:
db.js:
var exerciseNamesCache = [];
var exerciseNamesFields = [];
function getExerciseNames (callback) {
if (exerciseNamesCache.length > 0 && exerciseNamesFields.length > 0) {
callback(null, exerciseNamesCache, exerciseNamesFields);
}
pool.query('SELECT name FROM Exercises',function(error, results, fields){
if (!error) {
exerciseNamesCache = results;
exerciseNamesFields = fields;
}
callback(error, results, fields);
});
}
Обещания — это шаблон проектирования для обработки обратных вызовов. Он сравним с Java Futures (CompletionStage и т. д.), только намного легче. Если используемый вами API возвращает промис вместо принятия обратного вызова, вам нужно вызвать res.render() внутри метода промиса .then():
router.get('/', function(req, res, next) {
doSomethingAsync()
.then(function(result){
res.send(result);
})
.catch(next); // remember to pass on asynchronous errors to next()
});
Если API, который вы используете, принимает обратный вызов, то зависит от того, обернете ли вы его обещанием или нет, это скорее вопрос вкуса. Лично я бы не стал этого делать, если вы не используете другой API, который возвращает обещание.
Одним из преимуществ обещаний является то, что вы можете использовать их с await. Express особенно хорошо работает с async/await. Просто помните, что вы можете использовать await только внутри функции, помеченной async:
router.get('/', async function(req, res, next) {
let result = await doSomethingAsync();
res.send(result);
});
Извлечение нескольких асинхронных данных может быть таким простым, как:
router.get('/', function(req, res, next) {
doSomethingAsync(function(result1){
doSomethingElse(function(result2) {
res.json([result1, result2]);
});
});
});
С обещаниями, которые будут:
router.get('/', function(req, res, next) {
doSomethingAsync()
.then(function(result1){
return doSomethingElse()
.then(function(result2) {
return [result1, result2];
});
})
.then(function(results){
res.json(results);
})
.catch(next);
});
Но оба приведенных выше кода выполняют запросы последовательно (получить результат1, затем получить результат2). Если вы хотите получить оба данных параллельно, вы можете сделать это с помощью промисов:
router.get('/', function(req, res, next) {
Promise.all([
doSomethingAsync(), // fetch in parallel
doSomethingElse()
])
.then(function(results){
res.json(results);
});
})
С обратными вызовами немного сложнее. Существует шаблон проектирования, который вы можете использовать, и кто-то на самом деле реализовал его в виде библиотеки под названием асинхронный.js, но часто самое простое решение — обернуть их в промисы и использовать Promise.all(). Тем не менее, проверьте async.js, так как он имеет функциональные возможности, полезные для таких вещей, как пакетные запросы, выполнение асинхронных операций, пока условие истинно и т. д. (аналог этой библиотеки на основе обещаний — асинхронный Q)
Спасибо большое. Я очень ценю вашу помощь! У меня только один вопрос: таким образом, я смогу использовать результаты запроса в функции обратного вызова, тогда я буду вынужден вызывать res.render(...) внутри функции обратного вызова. Но что, если res.render нужны значения из разных запросов? Ища это в Интернете, я понимаю, что мне нужно использовать Promise.all или что-то в этом роде. Это правильный путь? Может быть, я не полностью закэшировал часть вашего ответа: например, действительно ли можно хранить результаты в переменной trainingNameCache?
С обещаниями, если вы хотите получать элементы параллельно, используйте Promise.all(), в противном случае связывайте свои обещания в .then(). С обратными вызовами просто вызовите другой обратный вызов в обратном вызове
Ооо, кажется, я понял! Спасибо!
@Lorenzoi Я добавил больше информации в свой ответ
Оберните код обещанием