Как организовать общение с базой данных в Node.Js?

У меня проблема с проектированием связи с базой данных 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-страниц.

Оберните код обещанием

Get Off My Lawn 08.07.2019 19:14

Используйте асинхронный/ожидающий синтаксис

Rashomon 08.07.2019 19:15

Я рекомендую вам использовать сиквел или другой ORM, это делает всю работу с БД намного чище.

l2ysho 08.07.2019 21:54
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
3
1 941
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать модули 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?

Lorenzoi 09.07.2019 12:46

С обещаниями, если вы хотите получать элементы параллельно, используйте Promise.all(), в противном случае связывайте свои обещания в .then(). С обратными вызовами просто вызовите другой обратный вызов в обратном вызове

slebetman 09.07.2019 12:50

Ооо, кажется, я понял! Спасибо!

Lorenzoi 09.07.2019 12:51

@Lorenzoi Я добавил больше информации в свой ответ

slebetman 09.07.2019 13:04

Другие вопросы по теме