在网上搜了一下,确定使用nodejs运行环境中的generic-pool库。
npm install generic-pool --save-dev
安装完成
这里没有什么太多的讲究,直接从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到项目中。
写了一个最简单的find查询:
mongoCRUD = {
findUser (client, query) {
return client.db('xxx').collection("users").find().toArray();
}
}
module.exports = mongoCRUD;
将mongoCRUD作为模块exports到项目中。
写这块代码的时候碰到了一个巨坑: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的基本教程演变而来的。
逻辑是这样的:
这里我还碰到另外一个问题,在我的代码结构里,authLogin是要返回一个信息是否匹配的消息到user.js控制器。而最终的匹配结果在最里层的
if(password != records[0]['password'])
才能获得这个信息,此时使用return信息是无法返回到最上层的authLogin函数中的(早就不是一个作用域了),而且这中回调函数中的return实际上已经意义不大了。
最后那个虚线那里我不确定是不是这样一个相应关系,但是不管怎么说,在callback的这种回调函数中,使用return语句已经无法把结果返回到调用者那里了,更况且多层的异步回调之后,很难找到原来的调用者了。
针对这个问题,我上面的代码是直接请教了公司的JS大佬,直接推荐使用的async/await方式来响应的。直接就写成了上面的代码形式。
但是我想针对这个Promise,还有async/await的使用区别做一个自己的理解记录,便于日后自己翻阅用(度娘了一把,感觉越讲越迷糊)。
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是干啥用的?这个下面会提到。
我们可以在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函数那边去的,记得带过去。
一层一层的往外调:
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();
上面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()函数,因为这个要真实耗掉一点时间才能出结果。
这个就让人很容易理解了。
我这里直接简写了一下,console.log(await called())
实际上就是let str = await called(),和普通的同步调用方法一致,但是确实是异步执行的。而语法形式上确实同步的写法。
async和await成对出现。调用者的函数头加上async,而在调用者调用被调者的使用,在被调者函数前面加上await就可以了。
我理解的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就大功告成了。
一下午就折腾出了一个连接池的处理函数,初步填了一个异步的坑。坑多多其修远兮,吾将上下而求索。