COMBO--组合拳打穿回调地狱~

586 查看

我想应该会有很多像我一样的前端听说js可以开发后台时,激动地踏上了node.js之路,这条路上第一个挑战,就是回调地狱。

app.get("/changePassword?**",function(req,res){
        if(req.cookies.username){
                pool.getConnection(function(err,connection){
                    if (err) {
                        console.log(err+"--from pool connection");
                        res.send("修改密码失败,数据库连接错误");
                    } else{
                        connection.query("USE userInfo",function(err,rows){
                            if (err) {
                                console.log(err+"--from using database");
                                res.send("修改密码失败,数据库使用错误");
                            } else{
                                var selectQuery = "SELECT * FROM users WHERE userName="+"'"+req.cookies.username+"'";
                                connection.query(selectQuery,function(err,rows){
                                    if (err) {
                                        console.log(err+"--from selectQuery");
                                        res.send("修改密码失败,数据库查询错误");
                                    } else{
                                        if (req.query.password==rows[0].password) {
                                            var updateQuery = "UPDATE users SET password="+"'"+req.query.newPassword+"' WHERE username="+"'"+req.cookies.username+"'";
                                            connection.query(updateQuery,function(err,rows){
                                                if (err) {
                                                    console.log(err+"--from updateQuery");
                                                    res.send("修改密码失败,数据库更新错误");
                                                } else{
                                                    res.send("修改密码成功");
                                                }
                                            });/*connection.query update end*/
                                        } else{
                                            res.send("修改密码失败,原始密码错误");
                                        }
                                    }
                                });/*connection.query select end*/
                            }
                        });/*connection.query using database end*/
                    }
                    if(connection){connection.release()};
                });/*pool.getConnection end*/
        } else {
            res.send("修改密码失败,登录失效");
        }
    });/*app.get end*/

    

这种造型的代码就是“邪恶金字塔”,或者说“回调地狱”,callback hell
我遇到的第一个障碍就是它,它让代码难以维护,难以修改,横向发展,非常不美观
于是我开始试图解决这个问题,为此,我求助了很多大神,看了很多帖子,被告知《ES6入门》这本书可以解决我的问题,于是从promise then到*yield到async/await
看到async/await我以为就皆大欢喜,问题解决了,然而nodejs目前需要babel转码才能使用async/await,很麻烦,而且对我这个新手很不友好。
在segmentfault上提问许久,发现有个asyncawait模块,可以模仿async/await模型来操作promise对象

npm install asyncawait

将如下代码添加到你的js文件中

var async = require("asyncawait/async");
var await = require("asyncawait/await");

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});

await()里面可以放promise对象,也可以放异步回调函数,只要它有类似的返回机制,这样一来,就能提前使用async/await模式写代码了,一开始的回调地狱会变得如下代码一样,清晰易懂

//登录路由
app.get("/loginForm?**", async(function(req, res) {
    try {
        var connection = await(poolp.getConnection());
        var selectQuery = "SELECT password FROM users WHERE username ='" + req.query.username + "'";
        var rows = await(connection.query(selectQuery));
        if (rows.length == 0) throw "登录失败,用户不存在";
        if (rows[0].password != req.query.password) {
            throw "登录失败,密码不正确";
        } else {
            res.send("登录成功");
        }
    } catch (err) {
        res.send(err);
    }
    //记得释放connection,不然很快就会达到上限
    if(connection) pool.releaseConnection(connection);
}));

不幸的是,await()里面放回调函数会使得代码很臃肿,如果放promise对象,就保持了与async/await模式的一致性。
nodejs的mysql模块,提供了pool,connection来操作数据库,可是它们都不是promise对象,我尝试自己封装成promise对象

var getConn = new Promise(function(resolve,reject){
        pool.getConnection(function(err,connection){
            if (err) {
                reject(err);
            } else {
                resolve(connection);
            }
        });
    });
    
    var DBobj = function(connection){
        this.connection = connection;
        this.query = (queryString)=>{
           var connPromise = new Promise(function(resolve, reject) {
                this.connection.query(queryString, function(err, rows) {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(rows);
                    }
                });
            });
            return connPromise;
        };
        return this;
    };
    

很蛋疼,而且DBobj无法正确返回对象,不过国外有大神早就解决了这个问题

npm install promise-mysql

var mysqlp = require('promise-mysql');
poolp = mysqlp.createPool({
  host: 'localhost',
  user: 'root',
  password: 'root',
  database: 'userInfo',
  connectionLimit: 10
});

就这么将mysql提供的对象转化为了promise对象,于是上面的登录路由就可以运行了,简洁明了,要加正则或者别的什么验证随时都能加,只需要在两行代码之间插入逻辑,再也不用框起一大片代码然后调缩进了!

相应的,fs模块,mail模块也应该有promise版本,大家可以去npm上面搜索

最后,我希望我的文章能帮助像我一样的小白打败回调地狱,一起踏上nodejs的探索之旅