【填坑日记】nodejs的数据库连接池 + JS异步机制(巨坑)

文章目录

  • 数据库连接池
    • 安装
    • 使用
      • mongoUntity
      • mongoCRUD
      • validate
  • JS异步调用机制
    • Promise应用场景
    • Promise返回参数
    • 嵌套情况下的resolver
    • async/await使用
  • 结语

接之前的一篇文章:
快速搭建基于VUE + EXPRESS + MONGODB架构系统
这里只搭建了一个基本的项目框架,将一个登录请求最基本的数据传到了后台并调用数据库。
今天的计划是写一个数据库操作的CRUD模块。
当然,在执行CRUD之前,计划在项目中使用数据库连接池的功能。在后台启动时就链接,与数据库做长连接,后续只要使用就可以了。没想到这一个东西搞了一下,最后请教了JS大佬才理解里面的东西。来吧,填坑吧。

数据库连接池

在网上搜了一下,确定使用nodejs运行环境中的generic-pool库。

安装

npm install generic-pool --save-dev

安装完成

使用

我自己的代码结构是:
代码结构

  • validate.js是一个action,响应router控制器转过来的请求。
  • mongoUntity是创建数据库的公共文件,暂时只有一个函数:创建连接池
  • mongoCRUD就是各种操作了。
    先看下创建连接池的代码。

mongoUntity

这里没有什么太多的讲究,直接从generic-pool官网上(https://www.npmjs.com/package/generic-pool)复制下来的代码。

const genericPool = require('generic-pool');
const factory = {
create: function() {
    return mongoClient.connect(mongoUrl, {useUnifiedTopology: true})
},
destroy: function (client) {
    client.close();
}
}

const opts = {
    max: 10,
    min: 2
}

const mypool = genericPool.createPool(factory, opts);

module.exports = mypool;

将mypool这个对象exports到项目中。

mongoCRUD

写了一个最简单的find查询:

mongoCRUD = {
    findUser (client, query) {
        return client.db('xxx').collection("users").find().toArray();
    }
}

module.exports = mongoCRUD;

将mongoCRUD作为模块exports到项目中。

validate

写这块代码的时候碰到了一个巨坑:JS异步调用机制,或者说callback回调机制。差点把自己绕进去。先把最后成型的代码贴出来(和generic-pool官网上的使用方式不同,教程是使用的Promise的方式,这里改成了async/await方式,这篇文章的重点也是讲这个东东。)

validate = {
  async authLogin(username, password) {
    let query = {'username':username};
    let success = 0; //0: success   1: failed

    const client = await mypool.acquire();
    let records = await mongoCRUD.findUser(client, query);
    if(records.length != 1) {
      console.log('wrong');
    }
    else{
      if(password != records[0]['password']) {
        success = 1;
      }
    }
    // resourcePro.then((client) => {
    //       mongoCRUD.findUser(client, query).then((records) => {
    //         if(records.length != 1) {
    //           console.log('wrong');
    //         }
    //         else{
    //           if(password != records[0]['password']) {
    //             success = 1;
    //           }
    //         }
    //       }).catch((err) => {
    //         console.log(err); //err on result
    //         success = 1;
    //       })
    //     }).catch((err) => { //error on acquire
    //       console.log(err);
    //       success = 1;
    //     });
    return success;
  }
}

module.exports = validate;

看到注释掉的那部分代码了吧,这是基于generic-pool的基本教程演变而来的。
逻辑是这样的:

  1. mypool.acquire()这个函数的返回值是一个Promise对象(Promise对象是啥请自行度娘,多得是)。处理Promise对象的标准玩法是:
    xxxPro.then((return_result) => {reslove 方法}).catch((err) => {reject方法})。
    关键点1:.then()返回一个新的Promise实例,.catch()也返回一个新的Promise实例,所以它可以链式调用。也就是可以写成xxxPro.then().catch().then().catch()…无限循环下去。
  2. mongoCRUD.findUser函数中的实际执行代码:client.db(‘xxx’).collection(“users”).find().toArray();也是返回一个Promise对象,这就有了mongoCRUD.findUser(client, query).then((records) 这一行代码。
    **关键点2:数据库的执行逻辑是获得链接(Promise对象) -> 发起链接(Promise) -> 执行查询(Promise)。每一步都会分成两个分支(.then / .catch)。**导致这样一个回调函数相当的绕。当然,这还算少的,如果逻辑再深几层,就会碰到那个巨坑:回调地狱。

这里我还碰到另外一个问题,在我的代码结构里,authLogin是要返回一个信息是否匹配的消息到user.js控制器。而最终的匹配结果在最里层的

if(password != records[0]['password'])

才能获得这个信息,此时使用return信息是无法返回到最上层的authLogin函数中的(早就不是一个作用域了),而且这中回调函数中的return实际上已经意义不大了。
【填坑日记】nodejs的数据库连接池 + JS异步机制(巨坑)_第1张图片
最后那个虚线那里我不确定是不是这样一个相应关系,但是不管怎么说,在callback的这种回调函数中,使用return语句已经无法把结果返回到调用者那里了,更况且多层的异步回调之后,很难找到原来的调用者了。

JS异步调用机制

针对这个问题,我上面的代码是直接请教了公司的JS大佬,直接推荐使用的async/await方式来响应的。直接就写成了上面的代码形式。
但是我想针对这个Promise,还有async/await的使用区别做一个自己的理解记录,便于日后自己翻阅用(度娘了一把,感觉越讲越迷糊)。

Promise应用场景

Promise是用于异步调用的,比如有两个函数:

function called(){
    console.log('I am the called function: begin');
    dosomething();
    console.log('I am the called function: end');
}

function caller(){
    console.log('I am the caller: begin');
    called();
    console.log('I am the caller: end');
}

caller函数要调用called函数,而called函数中有某些操作比较耗时(这里使用dosomething()来表示,假设这个函数要用时10s),而caller肯定是不能一直等待的,异步就是想要达到在called函数执行完成之后,再通知caller这边完成其他动作(回调:caller这边设置一个callback函数,让called函数执行完之后再调用callback)。
Promise就是提供了一样一种方便的语法定义,参考下面的代码:

function called(){
    return new Promise(function(resolve, reject){
        console.log('I am the called function: begin');
        //dosomething();
        console.log('I am the called function: end');
    });
}

function caller(){
    console.log('I am the caller: begin');
    called().then((rv) => {
        console.log(rv);
    }).catch((err) => {
        console.log(err);
    });
    console.log('I am the caller: end');
}

caller();

相当于在called函数中,直接return一个Promise对象,这个对象由运行环境来维护(nodejs),如果Promise对象定义的{ }中的代码执行成功(没有throw err),那么就由运行环境来调用.then()方法,否则则调用.catch方法。
大家可能会想那么new Promise里面用到的resolve和reject是干啥用的?这个下面会提到。

Promise返回参数

我们可以在called方法中增加一行语句:

function called(){
    return new Promise(function(resolve, reject){
        console.log('I am the called function: begin');
        //dosomething();
        console.log('I am the called function: end');

        return 'I am return value';
    });
}

这就和文章上面提到的一样,这个return是无法返回到caller方法中的。我刚开始以为的这段代码:

        called().then((rv) => {
	        console.log(rv);
	    }).

是可以将I am return value输出的,实际上是无法输出的,原因可以参考前面那副调用图。
好了,那么Promise的方案肯定是要支持return返回值的,当然也是可以的:

function called(){
    return new Promise(function(resolve, reject){
        console.log('I am the called function: begin');
        //dosomething();
        console.log('I am the called function: end');

        let str = 'I am return value';
        resolve(str);
    });
}

function caller(){
    console.log('I am the caller: begin');
    called().then((rv) => {
        console.log(rv);
    });
    console.log('I am the caller: end');
}

caller();

唯一的区别就在于把直接return改成了:

 let str = 'I am return value';
 resolve(str);

使用到了resolver这个东西。我对这个resolve函数的理解是:对运行环境说明一下,也就是给一些数据打上标记,这些数据是要转到.then函数那边去的,记得给我带过去。那么也可以推理出来,reject就是给一些数据打上标记,这些数据是要转到.catch函数那边去的,记得带过去。

嵌套情况下的resolver

一层一层的往外调:

function called_inner(){
    return new Promise(function(resolve, reject){
        console.log('I am the called_inner function: begin');
        //dosomething();
        console.log('I am the called_inner function: end');

        let str = 'I am return value_called_inner';
        resolve(str);
    });
}

function called(){
    return new Promise(function(resolve, reject){
        console.log('I am the called function: begin');
        //dosomething();
        called_inner().then((rv_inner) => {
            console.log(rv_inner);
        })
        console.log('I am the called function: end');

        let str = 'I am return value';
        resolve(str);
    });
}

function caller(){
    console.log('I am the caller: begin');
    called().then((rv) => {
        console.log(rv);
    });
    console.log('I am the caller: end');
}

caller();

【填坑日记】nodejs的数据库连接池 + JS异步机制(巨坑)_第2张图片

async/await使用

上面promise在解决异步的时候很容易陷入回调嵌套中,盯着代码看久了久容易晕。一般人还是适应于处理同步的逻辑,也就是昨晚第一步到第二步,然后第三步的这种节奏。
而且这种.then/.catch的方法嵌套多了也显得代码不够简练。这是,async/await的方式就出现了。
上面的代码可以改成:

function called_inner(){
    console.log('I am the called_inner function: begin');
    console.log('I am the called_inner function: end');

    return 'caller_inner return value';
}

async function called(){
    console.log('I am the called function: begin');
    console.log(await called_inner());
    console.log('I am the called function: end');

    
    return 'caller return value';
}

async function caller(){
    console.log('I am the caller function: begin');
    console.log(await called());
    console.log('I am the caller function: end');
}

caller();

这里把dosomething()改成了setTimeout()函数,因为这个要真实耗掉一点时间才能出结果。
【填坑日记】nodejs的数据库连接池 + JS异步机制(巨坑)_第3张图片
这个就让人很容易理解了。
我这里直接简写了一下,console.log(await called())
实际上就是let str = await called(),和普通的同步调用方法一致,但是确实是异步执行的。而语法形式上确实同步的写法。

  1. async和await成对出现。调用者的函数头加上async,而在调用者调用被调者的使用,在被调者函数前面加上await就可以了。

  2. 我理解的async和await就是运行环境自动判断了这个回调关系,帮我们封装了一次Promise的异步调用。我们可以对比一下:

     function caller(){
         console.log('I am the caller: begin');
         called().then((rv) => {
             console.log(rv);
         });
         console.log('I am the caller: end');
     }
    
     let rv = await called();
    

改造形式都可以固定下来,把.then中的那个变量放在前面,把被调用者直接写后面。然后加个await就大功告成了。

结语

一下午就折腾出了一个连接池的处理函数,初步填了一个异步的坑。坑多多其修远兮,吾将上下而求索。

你可能感兴趣的:(【填坑日记】nodejs的数据库连接池 + JS异步机制(巨坑))