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

说起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);
    });
}
时间: 2024-10-09 15:59:09

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

Effective JavaScript Item 19 使用高阶函数 (High-Order Function)

本系列作为Effective JavaScript的读书笔记. 不要被高阶函数这个名字给唬住了.实际上,高阶函数只是代表了两类函数: 接受其他函数作为参数的函数 返回值为函数的函数 有了这个定义,你也许就发现你已经使用过它们了,典型的就是对于一些事件的处理时传入的回调函数. 另外的一个典型使用场景就是Array类型的sort函数,它可以接受一个function作为排序时比较的判断依据: [3, 1, 4, 1, 5, 9].sort(function(x, y) { if (x < y) { r

高阶组件&amp;&amp;高阶函数(一)

antd里面的form表单方面,遇到一个高阶函数,以及高阶组件,于是看了一下这方面内容,前辈们的文章写得也非常详细,这里就稍微kobe一下 高阶函数与高阶组件 高阶函数: 高阶函数,是一种特别的函数,接受的参数为函数,返回值也是函数 成立条件,二者兼一即可 1).一类特别的函数 a).接受函数类型的参数 b).函数返回值是函数 常见的高阶函数: 2).常见 a).定时器:setTimeout()/setInterval() b).Promise:Promise(()=>{}) then(valu

Python学习笔记八:文件操作(续),文件编码与解码,函数,递归,函数式编程介绍,高阶函数

文件操作(续) 获得文件句柄位置,f.tell(),从0开始,按字符数计数 f.read(5),读取5个字符 返回文件句柄到某位置,f.seek(0) 文件在编辑过程中改变编码,f.detech() 获取文件编码,f.encoding() 获取文件在内存中的编号,f.fileno() 获取文件终端类型(tty.打印机等),f.isatty() 获取文件名,f.name() 判断文件句柄是否可移动(tty等不可移动),f.seekable() 判断文件是否可读,f.readable() 判断文件是

5.初识python装饰器 高阶函数+闭包+函数嵌套=装饰器

一.什么是装饰器? 实际上装饰器就是个函数,这个函数可以为其他函数提供附加的功能. 装饰器在给其他函数添加功能时,不会修改原函数的源代码,不会修改原函数的调用方式. 高阶函数+函数嵌套+闭包 = 装饰器 1.1什么是高阶函数? 1.1.1函数接收的参数,包涵一个函数名. 1.1.2 函数的返回值是一个函数名. 其实这两个条件都很好满足,下面就是一个高阶函数的例子. def test1(): print "hamasaki ayumi" def test2(func): return t

函数-内置函数,匿名函数,嵌套函数,高阶函数,序列化

函数简单说明 # 函数即"变量" # 高阶函数 # a.把一个函数名当做实参传给另一个函数(在不修改被装饰函数的源代码的情况下,为其添加功能) # b.返回值中包含函数名(不修改函数的调用方式) ''' import time def bar(): print("in the bar!") time.sleep(2) def foo(func): start_time = time.time() func() #根据内存地址,执行代码 stop_time = tim

Swift 中的高阶函数和函数嵌套

高阶函数 在Swift中,函数可做为"一等公民"的存在,也就意味着,我们可以和使用 int 以及 String 一样,将函数当做 参数.值.类型来使用. 其中,将函数当作一个参数和值来使用可见下: typealias addTwoInts = (Int,Int)->(Int) var funcType = addTwoInts.self func aAddb(a:Int,b:Int) -> Int { return a+b } func addFunc(_ add:addT

python第三天学习复习,集合set,文件操作,函数(普通函数,递归,高阶函数),字符编码和解码

三元运算 age = 23 #就是if else的简单写法 a = age if age < 20 else 25 集合 set #集合是无序切不重复的, #当对列表去重复的时候,可以直接使用 set(list),就将list转为set,并去除中间重复的 list = [1,2,3,4,5,5,6,7,8,9,1] s = set(list) 运行结果:可以发现将 list中重复的去掉,并且类型变成set,再使用list(set),转为list 集合操作 # Author:zylong set1

8-[函数]-嵌套函数,匿名函数,高阶函数,递归函数

1.嵌套函数 (1)多层函数套用 name = "Alex" def change_name(): name = "Alex2" def change_name2(): name = "Alex3" print("第3层打印", name) change_name2() # 调用内层函数 print("第2层打印", name) change_name() print("最外层打印",

装饰器,闭包,高阶函数,嵌套函数

高阶函数代码形式:(1.函数接收的参数是一个函数名 2.函数的返回值是一个函数名) def too(): print('from too') #高阶函数 def foo(): print('from foo') return too a = foo() a() def too(): print('from too') def foo(func): print('from foo') func() a = foo(too) 函数嵌套 定义一个函数,在该函数的代码块中在定义函数(可以一直定义下去)