取消嵌套节点数据库调用

Fat*_*tie 5 mysql nested node.js express

我有一个普通

var express = require('express')
Run Code Online (Sandbox Code Playgroud)

Node 表示 www 页面,像往常一样使用 session、pug 等。我的数据库调用

var db = require('./scripts/myHappyMysqlScript')
Run Code Online (Sandbox Code Playgroud)

我很自然地使用mysql,所以在 db 脚本中

var mysql = require('mysql')
Run Code Online (Sandbox Code Playgroud)

所以例如

app.get('/catPhotos', (req, response) => {
    response.render('catPhotos.pug');
})
Run Code Online (Sandbox Code Playgroud)

假设一个页面有一个表,显示了 petNames 数据库中的内容,

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

到目前为止一切都很好。

但是这里有一个例子,在 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
                })
        })
    })
})
Run Code Online (Sandbox Code Playgroud)

我只是这样嵌套它们。

这似乎工作得很好。

它正确地按顺序等待。错误发生并得到妥善处理,等等。

但是有没有更好的语法,更好的方法?对于真正的™ Node 而非 Swift 程序员来说,Node Way 是什么?!

也许考虑到我正在使用mysql图书馆,如果这是相关的。


请注意,总体而言,一种更好的方法是使用 Ajax 之类的东西在网页的每个“部分”中进行流式传输。事实上,我一直都在这样做。我在这里问的是,假设在 res.render 我确实想一次返回所有信息,有什么比这样嵌套更好的吗?干杯

You*_*saf 10

您可以使用promises.

由于您提到您正在使用mysql库与数据库进行交互,不幸的是,该库不提供基于承诺的 API。因此,要摆脱代码中的嵌套数据库调用,您需要围绕数据库调用的回调版本创建一个基于 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);
       });
   });
}
Run Code Online (Sandbox Code Playgroud)

现在在您的路由处理程序函数中,不是调用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
     }
});
Run Code Online (Sandbox Code Playgroud)

上面的代码示例仅显示了如何将db.cats(...)数据库调用包装在基于 Promise 的包装器中,并使用该包装器从数据库中获取数据。同样,您可以为db.dogs(...)db.budgies(...)调用创建包装器。

理想情况下,与其为每个数据库调用创建一个单独的基于 Promise 的包装器,不如创建一个可重用的基于 Promise 的包装器函数,该函数接受一个函数来调用并将该函数调用包装在一个承诺中,就像上面的代码所示例如,即getCats功能。

并行数据库调用

在路由处理函数中的上述代码中需要注意的一件重要事情

const cats = await getCats();
const dogs = await getDogs();
const budgies = await getBudgies();
Run Code Online (Sandbox Code Playgroud)

是这将导​​致顺序数据库调用,这可能是您想要的,也可能不是。

如果这些数据库调用不相互依赖,那么您可以使用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) {
       ...
     }
 });
Run Code Online (Sandbox Code Playgroud)

我希望这足以帮助您摆脱当前代码中的嵌套数据库调用,并开始在代码中使用 promise。