Press "Enter" to skip to content

node.js异步请求大坑

前段时间写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进行操作
    })
}
发表回复

您的电子邮箱地址不会被公开。