【问题标题】: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 我确实想一次返回所有这些信息,有没有比这样的嵌套更好的东西?干杯

【问题讨论】:

    标签: mysql node.js express nested


    【解决方案1】:

    您可以使用promises 摆脱嵌套的数据库调用。

    由于您提到您正在使用mysql 库与数据库进行交互,不幸的是,该库不提供基于 Promise 的 API。因此,要摆脱代码中的嵌套数据库调用,您需要围绕数据库调用的回调版本创建一个基于 Promise 的包装器。

    有关什么是 Promise 及其工作原理的总体概述,请参阅以下链接:

    以下是一个示例,说明如何创建基于 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 模块的改进版本。

    例如,对于执行查询,

    1. mysql
    con.query(sql_query, (err, rows, field)=>{ //some code here }
    
    1. 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 注入攻击)。要使用此功能,您必须为您的连接启用它。)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-07-13
          • 2021-01-03
          • 2018-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-23
          • 2021-10-16
          相关资源
          最近更新 更多