取消嵌套节点数据库调用
Posted
技术标签:
【中文标题】取消嵌套节点数据库调用【英文标题】:Unnesting Node database calls 【发布时间】:2021-02-04 10:13:58 【问题描述】:我有一个普通的
var express = require('express')
Node 表示 www 页面,像往常一样使用 session、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
)
)
)
)
我只是这样嵌套它们。
这似乎工作得很好。
它正确地按顺序等待。错误通过并得到正确处理,依此类推。
但是有更好的语法,更好的方法吗? 真正的™ Node(而非 Swift)程序员的 Node 方式是什么?!
也许考虑到我正在使用mysql
库,如果相关的话。
注意,总体而言,一种更好的方法是使用 Ajax 之类的东西在网页的每个“部分”中进行流式传输。确实,我一直都这样做。我在这里问什么,假设在 res.render 我确实想一次返回所有这些信息,有没有比这样的嵌套更好的东西?干杯
【问题讨论】:
【参考方案1】:您可以使用promises
摆脱嵌套的数据库调用。
由于您提到您正在使用mysql
库与数据库进行交互,不幸的是,该库不提供基于 Promise 的 API。因此,要摆脱代码中的嵌套数据库调用,您需要围绕数据库调用的回调版本创建一个基于 Promise 的包装器。
有关什么是 Promise 及其工作原理的总体概述,请参阅以下链接:
MDN - Promise
。
MDN - Using Promises
以下是一个示例,说明如何创建基于 Promise 的包装器,然后使用该包装器摆脱嵌套的数据库调用。
这个基于 Promise 的包装器只是一个返回 Promise 的函数。它创建一个 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 "resolve" function
// to resolve the promise. Promise will be resolved
// in case of successful database call
// Also pass the data to "resolve" function
// to access this data in the code that calls this
// "getCats" function
resolve(cats);
);
);
现在在您的路由处理函数中,调用 getCats
包装函数,而不是调用 db.cats(...)
。
有两种方法可以调用返回承诺的函数:
Promise-chaining
(详情请访问上述链接)
async-await
语法(推荐)
以下代码示例使用async-await
语法。为此,首先在function
关键字之前使用async
关键字将路由处理函数标记为async
。这样做,我们可以在这个路由处理函数中使用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(...)
调用创建包装器。
最好不要为每个数据库调用创建一个单独的基于 Promise 的包装器,理想情况下,您应该创建一个可重用的基于 Promise 的包装器函数,它接收要调用的函数并包装该函数调用在一个promise中,就像上面的代码示例所示,即getCats
函数。
并行数据库调用
在路由处理函数的上述代码中需要注意的一点
const cats = await getCats();
const dogs = await getDogs();
const budgies = await getBudgies();
这会导致连续的数据库调用,这可能是你想要的,也可能不是你想要的。
如果这些数据库调用不相互依赖,那么您可以使用Promise.all()
方法并行调用基于promise 的包装器。
以下代码示例展示了如何使用 Promise.all()
并行调用基于 Promise 的包装函数。
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)
...
);
我希望这足以帮助您摆脱当前代码中的嵌套数据库调用,并开始在您的代码中使用 Promise。
【讨论】:
这仍然感觉像是在链接。我认为第 1 步是获取数据,第 2 步是渲染。但即使是基于 Promise 的代码似乎也将渲染代码 inside 隐藏在 get-data 代码中,而不是 after 它。 @RickJames “即使是基于 Promise 的代码似乎也将渲染代码隐藏在 get-data 代码中,而不是在它之后” - 如果您查看try
块在路由处理程序的回调函数中,您将看到您在评论开头提到的两个步骤。基于 Promise 的代码只关心从数据库中获取数据,渲染代码在数据获取代码之后。 try
块中有一条注释应该写渲染代码。
我就是这么想的。有时我想做很多处理,包括一些 SQL 调用。我想要一个function
可以访问数据库并返回数据。由于我需要之前的结果,我什至不需要“异步”。
Parallel Database calls
就是这样。非常干净,易于维护且灵活。使用 Promise.all,如果任何调用失败,它都会快速失败。您可以查看其他 Promise 选项,并在部分失败但仍进行部分渲染时可能支持案例。【参考方案2】:
如果您尝试将 MySQL 与 Nodejs 一起使用,您应该寻找的模块是 mysql2
而不是 mysql
。
mysql2
提供了一种基于 promise 的方法,并且是用于 nodejs 的 mysql
模块的改进版本。
例如,对于执行查询,
-
在
mysql
con.query(sql_query, (err, rows, field)=> //some code here
-
在
mysql2
中,您可以使用异步方法和承诺方法。此外,mysql2 中的prepared statements 比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 & more docs
【讨论】:
【参考方案3】:如果您的数据库调用返回 Promise 而不是使用回调,您可以:
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()
]);
【讨论】:
Dylan,谢谢,我正在使用 'mysql' 库,所以... ?!?!?【参考方案4】:使用nodejs标准库util.promisify
简单地将mysql函数转换成promise
示例:
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()
方法似乎是一种更著名、更简洁的方法,可以像您的用例一样并行进行多个调用。
但还有另一种方法。 :Multiple statement queries
要使用此功能,您必须为您的连接启用它:
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 注入攻击)。要使用此功能,您必须为您的连接启用它。)
【讨论】:
以上是关于取消嵌套节点数据库调用的主要内容,如果未能解决你的问题,请参考以下文章
取消嵌套嵌套 json 数据以显示在 Quicksight 中