前段时间写Node.js执行mysql的时候踩了个大坑,大概就是nodejs请求Mysql数据表中的数据,返回以后,如果匹配正确就向另一个数据表中写数据。
Node.js express框架的一个get请求接口,具体操作是从数据库中检索验证码,如果正确就往另一个数据表中写入数据
原始代码:
app.get('/mailconfirm', function (req, res) {
const mail = req.query.mail;
const cap = req.query.cap;
var findmailsql = "SELECT * FROM CAP WHERE MAIL='" + mail + "'";
connection.query(findmailsql, function (err, result) {
if (err) {
console.log('[INSERT ERROR] - ', err.message);
return;
}
if (result == 0) {
res.send(eval('(' + '{"status":300,"msg":"NO EMAIL"}' + ')'));
} else {
let FindCapSql = "SELECT * FROM `CAP` WHERE MAIL='"+mail+"' AND CAP="+cap;
//= eval('(' + '{"status":300,"msg":"CAP FALSE"}' + ')');
connection.query(FindCapSql, function(err, result){
if (err) {
console.log('[INSERT ERROR] - ', err.message);
return;
}
if(result == 0){
res.send(eval('(' + '{"status":300,"msg":"CAP FALSE"}' + ')'));
} else {
console.log(result);
let insertEmailSql = 'UPDATE USERS SET MAIL = ? WHERE USER_ID = ?';
let insertEmailSqlValue = [mail, result.USER_ID];
connection.query(insertEmailSql, insertEmailSqlValue, function (err, result) {
if (err) {
console.log('[UPDATE ERROR] - ', err.message);
return;
}
console.log('test');
ret = result;
res.send(ret);
return;
})
}
})
/*
for (let i = 0; i < result.length; i++) {
if (result[i].CAP == cap) {
let insertEmailSql = 'UPDATE USERS SET MAIL = ? WHERE USER_ID = ?';
let insertEmailSqlValue = [mail, result[i].USER_ID];
connection.query(insertEmailSql, insertEmailSqlValue, function (err, result) {
if (err) {
console.log('[UPDATE ERROR] - ', err.message);
return;
}
console.log('test');
ret = result;
res.send(ret);
return;
})
}
}
console.log('test2');
//console.log(result.length);*/
}
//res.send(ret);
})
})
上面这段是修改后的代码,问题复现的结构大致如下:
for(let i=0; i<10; i++){
if(result[i] === 'test'){
connection.query(sql, function(err, result){
console.log('1');
})
}
}
console.log('2');
上述代码运行以后在进入for以后,由于mysql请求是异步请求,执行的时候控制台输出’2’会比mysql请求后输出‘1’提前执行,控制台会先输出2再输出1。
这里我想到是用Promise重新将接口进行包装,使得可以使用async/await进行调用,符合同步的编码习惯
1.promise封装接口
Promise 通常被定义为最终会变为可用值的代理。
Promise 是一种处理异步代码(而不会陷入回调地狱)的方式。
多年来,promise 已成为语言的一部分(在 ES2015 中进行了标准化和引入),并且最近变得更加集成,在 ES2017 中具有了 async 和 await。
异步函数 在底层使用了 promise,因此了解 promise 的工作方式是了解 async
和 await
的基础。
1.1 Promise 如何运作
当 promise 被调用后,它会以处理中状态开始。 这意味着调用的函数会继续执行,而 promise 仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。
被创建的 promise 最终会以被解决状态或被拒绝状态结束,并在完成时调用相应的回调函数(传给 then
和 catch
)。
1.3 Promise
Promise API 公开了一个 Promise 构造函数,可以使用 new Promise()
对其进行初始化:
let done = true
const isItDoneYet = new Promise((resolve, reject) => {
if (done) {
const workDone = '这是创建的东西'
resolve(workDone)
} else {
const why = '仍然在处理其他事情'
reject(why)
}
})
如你所见,promise 检查了 done
全局常量,如果为真,则 promise 进入被解决状态(因为调用了 resolve
回调);否则,则执行 reject
回调(将 promise 置于被拒绝状态)。 如果在执行路径中从未调用过这些函数之一,则 promise 会保持处理中状态。
使用 resolve
和 reject
,可以向调用者传达最终的 promise 状态以及该如何处理。 在上述示例中,只返回了一个字符串,但是它可以是一个对象,也可以为 null
。 由于已经在上述的代码片段中创建了 promise,因此它已经开始执行。 这对了解下面的消费 promise 章节很重要。
一个更常见的示例是一种被称为 Promisifying 的技术。 这项技术能够使用经典的 JavaScript 函数来接受回调并使其返回 promise:
const fs = require('fs')
const getFile = (fileName) => {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) {
reject(err) // 调用 `reject` 会导致 promise 失败,无论是否传入错误作为参数,
return // 且不再进行下去。
}
resolve(data)
})
})
}
getFile('/etc/passwd')
.then(data => console.log(data))
.catch(err => console.error(err))
现在,看看如何消费或使用 promise。
const isItDoneYet = new Promise(/* ... 如上所述 ... */)
//...
const checkIfItsDone = () => {
isItDoneYet
.then(ok => {
console.log(ok)
})
.catch(err => {
console.error(err)
})
}
运行 checkIfItsDone()
会指定当 isItDoneYet
promise 被解决(在 then
调用中)或被拒绝(在 catch
调用中)时执行的函数。
1.4 解决问题
使用链式promise处理两次异步mysql请求
基础的mysql异步调用如下:
function ControlAPI_obj(data, callback){
var sqlObj = _structureAnalysis(data);
dataBaseControl(sqlObj["sql"], sqlObj["value"], (result)=>{
if (result == null || result.length == 0) {
callback(null);
} else {
callback(result);
}
});
};
将上面的结构改写为同步调用:
// 传入单条SQL语句
var ControlAPI_obj_async = function(data) {
var sqlObj = _structureAnalysis(data);
return new Promise((resolved, rejected)=>{
dataBaseControl(sqlObj["sql"], sqlObj["value"], (result)=>{
if (result === null) {
rejected(null);
} else {
resolved(result);
}
});
});
}
// 传入多条SQL语句
var ControlAPI_objs_async = function(...vars) {
let len = vars.length;
let promiseList = [];
for(let i = 0; i < len; i++){
let sqlObj = _structureAnalysis(vars[i]);
promiseList.push(new Promise((resolved, rejected)=>{
dataBaseControl(sqlObj["sql"], sqlObj["value"], (result)=>{
if (result === null) {
rejected(null);
} else {
resolved(result);
}
});
}));
}
return Promise.all(promiseList);
使用方法
// 同步写法
async function(...){
[...]
try {
let result_single = await ControlAPI_obj_async(obj1);
let result_multi = await ControlAPI_objs_async(obj1, obj2,...,objn);
} catch (error) {
// 捕获await中Promise的reject的数据
}
[...]
}
// 异步写法
function(...) {
ControlAPI_obj(data, (result) => {
// 对result进行操作
})
}