Node.js es6 generator 和 thunk 函数解决异步金字塔

解决嵌套的回调

node 的异步确实强大,在单线程领域里那是做了相当不错的成就,有异步就有回调,如果多个回调嵌套在一起我们可能就束手无策了。看着都让人心烦。下面是一个异步”金字塔“,也称作异步”黑洞“。

假如我们要读取一个文件,然后等这个文件读取完毕再读取下一个文件,那么我们可能会这样写。

const fs = require("fs");
fs.readFile("./data1.txt","utf8",(err, data) => {
    fs.readFile("./data2.txt","utf8",(err, data) => {
        fs.readFile("./data3.txt","utf8",(err, data) => {
            fs.readFile("./data4.txt","utf8",(err, data) => {
                ...//more codes
            });
        });
    });
});

这样显然是看着就让人不舒服,而且也不利于代码的维护和可读性。
在解决这个难题之前我们先要来认识一下thunk

thunk函数

Thunk函数在20世纪60年代就诞生了。
当时设计语言的时候涉及到参数的求值策略。比如下面是一段简单的js代码

let func = function(s){
    console.log(s);
}

func(1+1);

那参数究竟应该怎么去求值呢,当时流行着两种方法。

传值策略

C/C++ 就是用的这种传递方式,意思是先把参数的值进行计算再进行传递。这种方式的好处就是简单。

传名策略

第二种就是传名策略,js是用的这种策略,意思是在需要参数参与计算的时候再把它求值,这种策略不容易造成损失。

那究竟这种策略是什么实现的呢。实际上是把参数保存在了一个函数中,等需要用的时候再从这个函数中把值返回出来,假如我们传递进去的是个加法表达式,它的样子大概是这样的。

    let func = function(a,b){
        return a+b;
    }

事实上这个func就是thunk函数,thunk函数就是用来保存参数表达式的。

ES6里的Thunk函数

es6里也提供了thunk函数,但是它的函数含义有所不同,它替换的不是参数表达式,而是多参函数,把多参函数变为单参函数,这个单参函数的参数就是一个回调函数。

我们先来自己实现一个thunk函数。

const fs = require("fs");
//thunk
let Thunk = function(path){
    return function(callback){
        return fs.readFile(path,callback);
    }
}

//调用thunk
let readFile = Thunk("./data.txt");
//此时readFile就是一个单参函数
readFile((err,data) => {
    console.log(data.toString());
});

这时我们自己实现了一个thunk函数,用它来把多参函数变为单参函数。

在node里为我们提供了一个模块叫做thunkify模块。我们只需要用下面的命令将模块部署到我们项目的node_modules 里即可

$ npm install --save thunkify

等待安装完毕即可。

接下来我们在代码里面使用它

const fs = require("fs");
const thunkify = require("thunkify");
//thunk
let Thunk = thunkify(fs.readFile);

//调用thunk
let readFile = Thunk("./data.txt");
//此时readFile就是一个单参函数
readFile((err,data) => {
    console.log(data.toString());
});

它帮助我们省下了写转换器的步骤,其实它内部就封装了一个thunk转换器,感兴趣的读者可以去看一下它的内部源码,我这里就不再累述了。

Generator函数

这是es6 新添加的一个函数,它其实是一个对协程的实现。在几个协程中交换控制权。

看一下一个简单的Generator函数

let gen = function*(){
    y = yield 1+1;
    x = yield 2;
}

let it = gen();
console.log(it.next());
console.log(it.next().value);

generator函数的实现十分简单,只需在传入参数的第一个括号前加上* 即可示意这是一个generator函数。
而整个函数的精髓在于yield 关键字,网上对这个单词的翻译有很多种,我更喜欢将它翻译为投降,意思是在执行到这一句的时候,发现了yield关键字,即交付控制权,并等待控制权返回。再返回后继续执行下面的语句。

奇怪的是当我们执行generator函数并不会得到我们期望的结果,而是得到一个此函数的内部指针,类似于迭代器,这个内部指针提供了一个next方法,用于下移指针,直到遇到yield关键字为止,交付控制权,暂停执行。

说了这么多我们看一下上面代码执行后的返回结果

{ value: 2, done: false }
2

这个内部指针返回的是一个json对象,该对象有两个属性,第一个属性为value,即出现yield语句的值,第二属性为done ,它的value是一个boolean值,来判断该内部指针是否已经指向了最底部。

我们打印一下第二条语句的done看看便知

false

虽然第二条语句是最后一条语句,但此时它已经交付了控制权,并不代表它被执行完毕。
因此我们再次执行

console(it.next().done);

output:

true

这下才算是真正的执行完毕。

generator和thunk解决回调金字塔

此时我们大概的明白了thunk 和 generator的使用方法,接下来我们看一下它们是如何来解决回调金字塔的。

const fs = require("fs");
const thunkify = require("thunkify");
//把readFile转换为单参函数
let thunk = thunkify(fs.readFile);
//创建generator函数
let gen = function*(){
    let r1 = yield thunk("./data.txt","utf8");
    console.log(r1);
    let r2 = yield thunk("./data2.txt","utf8");
    console.log(r2);
    let r3 = yield thunk("./data3.txt","utf8");
    console.log(r3);
};

//获取到generator函数的内部指针
let it = gen();
let result = it.next();
//传入callback
result.value((err,data) => {
    //指针下移
    result = it.next(data);
    result.value((err,data) => {
        //指针下移
        result = it.next(data);
        result.value((err,data) => {
            //指针下移
            result = it.next(data);
            //此时已经执行完毕
        });
    });
});

output:

data1
data2
data3

现在我们已经实现了generator 和thunk来解决回调嵌套,不过到现在看来,似乎没有什么大的改观,还是看起来可读性差和难以维护,此时我们可以借助递归来实现generator函数的自动执行

//加载模块
const fs = require("fs");
const thunkify = require("thunkify");
//创建thunk函数
let readFile = thunkify(fs.readFile);
//create generator
let gen = function*(){
    let r1 = yield readFile("./data.txt","utf8");
    //打印结果
    console.log(r1);
    let r2 = yield readFile("./data2.txt","utf8");
    //打印结果
    console.log(r2);
    let r3 = yield readFile("./data3.txt","utf8");
    //打印结果
    console.log(r3);
};

//递归自动执行函数
let run = function(){
    //获取内部指针
    let fn = gen();
    let next = function(err,data){
        //指针下移
        let result = fn.next(data);
        //递归出口
        if(result.done){
            return;
        }
        result.value(next);
    };
    next();
};

//自动执行
run();

我们将自动执行器封装为一个函数,可提高我们的代码重用,此时,我们已经差不多解决掉 回调金字塔

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值