首页 > 代码库 > 优雅编码,拒绝嵌套——高阶函数的一种应用

优雅编码,拒绝嵌套——高阶函数的一种应用

说起javascript编码的嵌套问题,大多数人会想到由于异步编程导致的回调函数嵌套:

//open dbdb.open((err, db) => {    if(!err){        //create collection        db.createCollection(‘myCollection‘, {safe:true}, (err, collection) => {            if(err){                console.log(err);            }else{                // query data...                collection.find().toArray((err,docs) => {                    // do something...                });            }        });    }else{        console.log(err);    }});

回调函数嵌套的代码不仅难以阅读维护,也难以解耦、扩展。

针对此情况,有多种解决办法,如:ES6的promise特性,eventproxy模块,async模块

现以async模块为例:

var async = require(‘async‘);var openDB = (callback, results) => {    //open db    db.open((err, db) => {        callback(err, db);    }}var createCollection = (callback, results) => {    //create collection    results[‘openDB‘].createCollection(‘myCollection‘, {safe:true}, (err, collection) => {        callback(err, collection);    });}var queryData = http://www.mamicode.com/(callback, results) => {    //query data    results[‘createCollection‘].find().toArray((err,docs) => {        callback(err, docs);    });}module.exports = (req, res) => {    async.auto({        ‘openDB‘: openDB,        ‘createCollection‘: [‘openDB‘, createCollection],        ‘queryData‘: [‘createCollection‘, queryData]    }, (err, results) => {        console.log(err);    });}

可以发现,使用async模块后,回调函数的嵌套问题得以解决,不同逻辑之间的依赖一目了然,逻辑显得十分清晰。

如果希望给以上逻辑中增加一个功能,将查询到的数据发送给客户端,也十分简单,只需要添加一个函数:

var sendData = http://www.mamicode.com/(callback, results) => {    res.send(results[‘queryData‘]);}

然后在async的入参中添加sendData:

module.exports = (req, res) => {    async.auto({        ‘openDB‘: openDB,        ‘createCollection‘: [‘openDB‘, createCollection],        ‘queryData‘: [‘createCollection‘, queryData],        ‘sendData‘: [‘queryData‘, sendData]    }, (err, results) => {        console.log(err);    });}

这里出现一个问题,由于处于不同作用域,发送数据所需使用的res.send无法被sendData函数直接调用。

而sendData函数被async.auto调用的时候,会被强制传入callback, results两个参数。

因而无法使用bind传入res:

‘sendData‘: [‘queryData‘, sendData.bind(this, res)]

这种方案行不通。

最简单的解决方案是创建一个新的匿名函数,使用闭包扩大res的作用域:

module.exports = (req, res) => {    async.auto({        ‘openDB‘: openDB,        ‘createCollection‘: [‘openDB‘, createCollection],        ‘queryData‘: [‘createCollection‘, queryData],        ‘sendData‘: [‘queryData‘, (callback, results) => {            sendData(callback, results, res);        }]    }, (err, results) => {        console.log(err);    });}

再改写sendData的入参:

var sendData = http://www.mamicode.com/(callback, results, res) => {    res.send(results[‘queryData‘]);}

问题看似已经解决,然而付出的代价是创建了一个匿名函数,多了一层嵌套。

是否有方法,可以写出更加简洁的代码呢?


 

现在将遇到的问题提炼一下:

我们希望sendData函数接收3个参数。

其中1个参数,是在调用async.auto之前传入

另外2个参数,是在async.auto执行中传入。

显而易见,这种问题可以使用高阶函数解决。

改写sendData函数:

var sendData = http://www.mamicode.com/(res) => (callback, results) => {    res.send(results[‘queryData‘]);}

改写async.auto的入参:

module.exports = (req, res) => {    async.auto({        ‘openDB‘: openDB,        ‘createCollection‘: [‘openDB‘, createCollection],        ‘queryData‘: [‘createCollection‘, queryData],        ‘sendData‘: [‘queryData‘, sendData(res)]    }, (err, results) => {        console.log(err);    });}

问题解决,不再需要多写一个匿名函数。

但是对于每一个类似sendData的函数都需要如此处理,显得十分麻烦。

因此可以做一个批量封装的函数:

var packageFuncs = (functions) => {    var newFunctions = {};    typeof functions == ‘object‘ &&    Object.keys(functions).forEach((key) => {        if (typeof functions[key] == ‘function‘) {            //封装新函数            newFunctions[key] = (...params1) => (...params2) => {                functions[key](...params2.concat(params1));            }        }    });    return newFunctions;}

封装所有需要的函数:

pacakageFuncs({    openDB,    createCollection,    queryData,    sendData});

完整的代码如下(拆成3个模块):

common.js

module.exports.packageFuncs = (functions) => {    var newFunctions = {};    typeof functions == ‘object‘ &&    Object.keys(functions).forEach((key) => {        if (typeof functions[key] == ‘function‘) {            //封装新函数            newFunctions[key] = (...params1) => (...params2) => {                functions[key](...params2.concat(params1));            }        }    });    return newFunctions;}

functions.js

var packageFuncs = require(‘./common‘).packageFuncs;var openDB = (callback, results) => {    //open db    db.open((err, db) => {        callback(err, db);    }}var createCollection = (callback, results) => {    //create collection    results[‘openDB‘].createCollection(‘myCollection‘, {safe:true}, (err, collection) => {        callback(err, collection);    });}var queryData = http://www.mamicode.com/(callback, results) => {    //query data    results[‘createCollection‘].find().toArray((err,docs) => {        callback(err, docs);    });}var sendData = http://www.mamicode.com/(callback, results, res) => {    res.send(results[‘queryData‘]);}module.exports = pacakageFuncs({    openDB,    createCollection,    queryData,    sendData});

controller.js

var async = require(‘async‘);var {openDB, createCollection, queryData, sendData} = require(‘./functions‘);module.exports = (req, res) => {    async.auto({        ‘openDB‘: openDB,        ‘createCollection‘: [‘openDB‘, createCollection],        ‘queryData‘: [‘createCollection‘, queryData],        ‘sendData‘: [‘queryData‘, sendData(res)]    }, (err, results) => {        console.log(err);    });}

 

优雅编码,拒绝嵌套——高阶函数的一种应用