Ненужные вызовы базы данных узлов

#mysql #node.js #express #nested

#mysql #node.js #экспресс #вложенный

Вопрос:

У меня обычный

 var express = require('express')
  

Узел выражает страницу www, используя сеанс, pug и т. Д., Как Обычно. Мои вызовы БД

 var db = require('./scripts/myHappyMysqlScript')
  

Я, естественно, использую mysql , поэтому в скрипте db

 var mysql = require('mysql')
  

Так, например

 app.get('/catPhotos', (req, response) => {
    response.render('catPhotos.pug');
})
  

Скажем, на странице есть таблица, показывающая что-то из базы данных petNames,

 app.get('/pets', function(req, res, next) {
    db.allPetNames(function(err, petsList) {
        res.render('pets.pug',
            {
                'petsList': petsList,
                'pretty' : true
            })
    })
  

пока все хорошо.

Но вот случай с тремя таблицами на странице pug и тремя разными вызовами базы данных:

 db.cats(function(err, c) {
    db.dogs(function(err, d) {
        db.budgies(function(err, b) {
            res.render('bigScreen.pug',
                {
                    'cats' : c,
                    'k9s': d,
                    'budgies': b,
                    'pretty' : true
                })
        })
    })
})
  

Я просто вкладываю их так.

Похоже, это работает отлично.

Он корректно ожидает последовательно. Ошибки проходят и обрабатываются должным образом и так далее.

Но есть ли лучший синтаксис, лучший способ? Каков путь узла для программистов real ™ Node, а не Swift ?!

Возможно, учитывая, что я использую mysql библиотеку, если это уместно.


Обратите внимание, что один из лучших способов в целом — использовать что-то вроде Ajax для простой потоковой передачи в каждой «части» веб-страницы. Действительно, я делаю это все время. То, что я спрашиваю здесь, предполагая, что в res.render я действительно хочу вернуть всю эту информацию сразу, есть ли что-то лучше, чем такое вложение? Приветствия

Ответ №1:

Вы можете избавиться от вложенных вызовов базы данных с помощью promises.

Поскольку вы упомянули, что используете библиотеку mysql для взаимодействия с базой данных, к сожалению, эта библиотека не предоставляет API на основе обещаний. Итак, чтобы избавиться от вложенных вызовов базы данных в вашем коде, вам нужно создать оболочку на основе обещаний вокруг версии обратного вызова вызовов базы данных.

Для общего обзора того, что такое promises и как они работают, см. Следующие ссылки:

Ниже приведен пример того, как вы можете создать оболочку на основе обещаний, а затем использовать эту оболочку, чтобы избавиться от вложенных вызовов базы данных.

Эта оболочка на основе обещаний — это просто функция, которая возвращает обещание. Он создает экземпляр promise, завершает базовый вызов базы данных и, в конечном итоге, когда вызов базы данных возвращает данные, уведомляет ваш код.

 function getCats() {
   return new Promise((resolve, reject) => {
       // make the database call
       db.cats((error, cats) => {
           // in case of an error, reject the promise by
           // calling "reject" function
           // Also pass the "error" object to the "reject" function
           // as an argument to get access to the error message 
           // in the code that calls this "getCats" function
           if (error) {
              reject(error);
              return;
           }
           
           // if there was no error, call the "resolve" function
           // to resolve the promise. Promise will be resolved 
           // in case of a successful database call
           // Also pass the data to the "resolve" function
           // to access this data in the code that calls this
           // "getCats" function
           resolve(cats);
       });
   });
}
  

Теперь в вашей функции обработчика маршрута вместо вызова db.cats(...) вызовите эту getCats функцию-оболочку.

Есть два способа вызвать функцию, которая возвращает обещание:

  • Promise-chaining (Для получения подробной информации посетите ссылки, упомянутые выше)
  • async-await синтаксис (рекомендуется)

В следующем примере кода используется async-await синтаксис. Для этого сначала отметьте функцию обработчика маршрута как async , используя async ключевое слово перед function ключевым словом. Делая это, мы можем использовать await ключевое слово внутри этой функции обработчика маршрута.

 app.get('/pets', async function(req, res, next) {
    try {
       const cats = await getCats();
       // similar wrappers for other database calls
       const dogs = await getDogs();
       const budgies = await getBudgies();
       
       // render the pub template, passing in the data
       // fetched from the database 
       ...

     catch (error) {
       // catch block will be invoked if the promise returned by
       // the promise-based wrapper function is rejected
       // handle the error appropriately
     }
});
  

Приведенный выше пример кода показывает только, как обернуть db.cats(...) вызов базы данных в оболочку на основе обещаний и использовать эту оболочку для получения данных из базы данных. Аналогично, вы можете создавать оболочки для db.dogs(...) db.budgies(...) вызовов и .

Вместо создания отдельной оболочки на основе обещаний для каждого вызова базы данных, в идеале, вы должны создать повторно используемую функцию-оболочку на основе обещаний, которая принимает вызываемую функцию и оборачивает этот вызов функции в обещание, как показано в приведенном выше примере кода, т.Е. getCats function .

Параллельные вызовы базы данных

Одна важная вещь, на которую следует обратить внимание в приведенном выше коде, — это функция обработчика маршрута

 const cats = await getCats();
const dogs = await getDogs();
const budgies = await getBudgies();
  

заключается в том, что это приведет к последовательным вызовам базы данных, которые могут быть или не быть тем, что вы хотите.

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

Следующий пример кода показывает, как вы можете вызывать свои функции-оболочки на основе обещаний параллельно с использованием Promise.all() .

 app.get('/pets', async function(req, res, next) {
    try {
       // "petsData" will be an array that will contain all the data from 
       // three database calls.
       const petsData = await Promise.all([getCats(), getDogs(), getBudgies()]);
       
       // render the pub template, passing in the data
       // fetched from the database 
       ...
 
     catch (error) {
       ...
     }
 });
  

Я надеюсь, что этого достаточно, чтобы помочь вам избавиться от вложенных вызовов базы данных в вашем текущем коде и начать использовать promises в вашем коде.

Комментарии:

1. Это все еще похоже на цепочку. Я думаю, что шаг 1 — это получение данных, шаг 2 — рендеринг. Но даже код, основанный на обещаниях, похоже, скрывает код рендеринга внутри кода get-data, а не после него.

2. @RickJames «даже код, основанный на обещаниях, похоже, скрывает код рендеринга внутри кода получения данных, а не после него» — если вы посмотрите на код в try блоке внутри функции обратного вызова обработчика маршрута, вы увидите два шага, которые вы упомянули в начале своего комментария. Код на основе обещаний просто связан с получением данных из базы данных, а код рендеринга — после кода выборки данных. В try блоке, где должен быть написан код рендеринга, есть комментарий.

3. Это то, что я подумал. Бывают случаи, когда я хочу выполнить большую обработку, включая некоторое количество вызовов SQL. Я хотел бы иметь a function , который обращается к базе данных и возвращается с данными. Поскольку мне нужны результаты до предыдущего, мне даже не нужен «async».

4. Parallel Database calls Это путь. Очень чистый, простой в обслуживании и гибкий. С обещанием. все это быстро завершится неудачей, если какой-либо из вызовов завершится неудачей. Вы могли бы посмотреть на другие варианты Promise и потенциально поддержать случай, когда у вас частичный сбой и все равно выполняется частичный рендеринг.

Ответ №2:

Если вы пытаетесь использовать MySQL с Nodejs, модуль, который вы должны искать mysql2 , — это, а не mysql .

mysql2 обеспечивает подход, основанный на обещаниях, и представляет собой значительно усовершенствованную версию mysql module для nodejs.

Например, для выполнения запроса,

  1. в mysql
 con.query(sql_query, (err, rows, field)=>{ //some code here }
  
  1. в mysql2 , вы можете использовать асинхронный подход, а также подход promise. Кроме того, подготовленные инструкции в mysql2 более просты, чем mysql.
 //async approach
class A {
   static async fn(sql, params){
      const [data] = await con.execute(sql, [params]);
    return data;
    }
}

//promise approach remains same as **mysql** itself.
  

Вот документация для
mysql2
и другие документы

Ответ №3:

Если ваши вызовы базы данных возвращали обещания вместо использования обратных вызовов, вы могли бы:

 const cats = await db.cats();
const dogs = await db.dogs();
const budgies = await db.budgies();

res.render('bigScreen.pug', {
  cats : cats,
  k9s: dogs,
  budgies: budgies,
  pretty : true
});


// Or request them all in parallel instead of waiting for each to finish
const [
  cats,
  dogs,
  budgies
] = Promise.all([
  dg.cats(),
  dg.dogs(),
  db.budgies()
]);
  

Комментарии:

1. Дилан, спасибо, я использую библиотеку ‘mysql’, так что … ?!?!?

Ответ №4:

Просто преобразуйте функции mysql в promises, используя стандартную библиотеку nodejs util.promisify

пример:

 const { promisify } = require('util');

const catsPromise = promisify(db.cats);
const dogsPromise = promisify(db.dogs);
const budgiesPromise = promisify(db.budgies);

async function routeHandler() {
  let err = null;

  try {
    const cats = await catsPromise();
    const dogs = await dogsPromise();
    const budgies = await budgiesPromise();
  } catch(error) {
    err = error;
  }

  if (err) {
    console.log(err);
    // you should res.end() or res.render(someErrorPage) here
    // failure to do so will leave the request open
  } else {
    res.render('bigScreen.pug', {
      'cats' : cats,
      'k9s': dogs,
      'budgies': budgies,
      'pretty' : true
    });
  }
}
  

Ответ №5:

Promise.all() метод кажется более известным и более чистым способом параллельного выполнения нескольких вызовов, как в вашем случае использования.

Но есть еще один альтернативный способ. : Несколько запросов операторов

Чтобы использовать эту функцию, вы должны включить ее для вашего соединения:

 var connection = mysql.createConnection({multipleStatements: true});
  

После включения вы можете выполнять несколько запросов операторов, как и любой другой запрос:

 db.query('SELECT cats; SELECT dogs', function (error, results, fields) {
  if (error) throw error;
  // `results` is an array with one element for every statement in the query:
  console.log(results[0]); // [{cat1,cat2}]
  console.log(results[1]); // [{dog1,dog2}]
});
  

Это технически более эффективно, поскольку требует меньше переходов назад и вперед с подключением к MySQL.

(Однако эта функция по умолчанию отключена, поскольку она допускает атаки с использованием SQL-инъекций, если значения не экранируются должным образом). Чтобы использовать эту функцию, вы должны включить ее для вашего соединения.)