Задумывались ли вы, почему все веб-фреймворки в узле требуют, чтобы вы возвращали ответы, используя объект 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
}
exerc.js
let db = require('./db');
// you can now use db.pool in the rest of your code
// ..
Повторное использование ваших запросов
Вместо того, чтобы кодировать операторы SELECT в ваших контроллерах (маршрутах), вы можете (и должны) кодировать их в ваших модулях дб:
дб.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);
});
}
Обещания
Обещания - это шаблон дизайна дляобработка обратных вызовов.Это сравнимо с Futures Java (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, который возвращает обещание.
async / await
Одним из преимуществ обещаний является то, что вы можете использовать их с 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).Если вы хотите получать обе данные параллельно, вы можете сделать это с помощью Promises:
router.get('/', function(req, res, next) {
Promise.all([
doSomethingAsync(), // fetch in parallel
doSomethingElse()
])
.then(function(results){
res.json(results);
});
})
С обратными вызовами это немного сложнее.Существует шаблон проектирования, который вы можете использовать, и кто-то фактически реализовал его как библиотеку под названием async.js , но зачастую самым простым решением является обернуть их в Promises и использовать Promise.all()
.Тем не менее, проверьте async.js, поскольку он имеет функциональные возможности, полезные для таких вещей, как пакетные запросы, выполнение асинхронных операций, когда условие истинно и т. Д. (Аналог обещания для этой библиотеки: async-q )