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


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

    //open db
    db.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 = (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 = (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 = (callback, results, res) => {
        res.send(results['queryData']);
    }

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

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


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

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

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

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

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

    改写sendData函数:

    var sendData = (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 = (callback, results) => {
        //query data
        results['createCollection'].find().toArray((err,docs) => {
            callback(err, docs);
        });
    }
    
    var sendData = (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);
        });
    }
  • 相关阅读:
    spring-cloud-feign案例
    spring-cloud-hystrix熔断
    科技创业公司最爱的9大工具箱
    近期入手一树莓派卡片机,体验了一下它的强大,写篇报告
    Android应用盈利广告平台的嵌入方法详解
    UnityVS(Visual Studio Tools For Unity)的安装与使用
    unity3d快捷键大全
    Unity3D Android手机开发环境配置,可真机发布调试
    谷歌最近收购的机器人公司
    Microsoft Robotics Developer Studio 4
  • 原文地址:https://www.cnblogs.com/nantang/p/5920802.html
Copyright © 2020-2023  润新知