源码共读感悟,JS”代码嵌套“思想~

1,381 阅读4分钟

原文: lucasfe.cn

一、何为“代码嵌套”思想 ?

首先什么是”代码嵌套“思想呢?用语言描述来讲,就是构造一个有着嵌套逻辑的等待执行的函数构造器,这么说可能比较抽象,不太好理解,那我用reduce手写compose函数来带着大家领悟一下这种思想

function compose(...fn) {
    return fn.reduce((prev, next) => {
        return function(...args) {
            return prev(next(...args))
        }
    })
}

如果不用compose方法,我们将要函数包函数的执行代码。只有两个函数看起来不是很繁琐,但是包装函数多了,就会很乱

// 代码使用:
function inputNumber(num) {
    return Number(num);
}
function addUnit(num) {
    return num + '元';
}

const result = inputNumber(addUnit(100)); // 100元

但是如果用compose函数,代码就会从树状结构变成了扁平的结构,这种方式也更容易拓展

const result = compose(addUnit, inputNumber)(100); // 100 元

从compose的实现上也可以看出来,主要方法就是构造一个嵌套的函数构造器,compose返回的构造器如下:

function(...args) {
    return addUnit(inputNumber(...args));
}

二、抛出问题

  • 什么是手动“代码嵌套”?
  • 什么是自动”代码嵌套”?

三、CO源码

在当时还没有async await规范的时候,为了避免Promise回调地狱的出现,会用generator配合CO来解决

// CO的使用
const fs = require('fs').promises;

function* read() {
    const a = yield fs.readFile('./a.txt', 'utf-8') ~// a = ‘b.txt’
    const b = yield fs.readFile(a, 'utf-8')
    return b;
}


co(read()).then(res => {
    console.log(res); // 此时会返回
})

来手写一下CO方法!主要原理就是,返回一个Promise,无脑触发iterator的next方法,直到done的时候触发resolve

// it是传入的生成器函数,例如上面写的read函数
function co(it) {
    return new Promise((resolve, reject) => {
        function next(data) {
            const { value, done } = it.next(data);
            if(done) {
                resolve(value);
                return;
            }
            // 执行yield右边的函数
            Promise.resovle(value).then(res => {
                next(res);
            }, reject);
        }
        // 第一次不需要给generator传值
        next();
    })
}

CO函数就是典型的自动“代码嵌套”,因为函数会无脑的触发next函数,而不是用户手动来触发。接下来我来介绍另一种方式,手动“代码嵌套”(又称用户执行“代码嵌套”)

四、Koa部分源码

Koa 是大名鼎鼎的 Node.js 的拓展库,Koa主要整合了Node.js http模块中比较鸡肋的地方。这篇文章主要介绍Koa中间件的一些实现原理,中间件的实践不深入讨论。

首先,介绍一下Koa中间件的用法:

const koa = require('koa')
const app = new koa();

app.use(async (ctx, next) => {
    console.log(1)
    await next()
    console.log(2)
})

app.use(async (ctx, next) => {
    console.log(3)
    await next()
    console.log(4)
})

app.listen(3000); // 1 3 4 2

这段代码大家可以运行一下,输出后的结果应该是1 3 4 2。因为Koa中注册的中间件,会按照注册顺序进行执行,调用next方法会执行下一次注册的中间件。需要注意的是,尽量使用await next(),否则会执行顺序会错乱,例如:第二个注册函数中,next()之前有异步函数,这样第一个函数不会等待第二个函数整体执行完,结果会变成1 3 2 4

Koa中间件的实现:

const http = require('http');

class Koa {
    constructor() {
        this.middlewares = [];
    }
    use(fn) {
        this.middlewares.push(fn);
    }
    compose(ctx) {
        function next(index) {
            if(index === this.middlewares.length) return Promise.resolve();
            // 执行注册的函数
            // 传入的第二个参数,用于用户手动执行next函数
            return Promise.resovle(this.middlewares[index](ctx, () => next(index + 1)))
        }
    
        return next(0)
    }
    handleRequest = (req, res) => {
        // this.createContext方法就不写了,主要作用是创建上下文
        const ctx = this.createContext(req, res);
        
        // 主要看compose函数
        this.compose(ctx).then(() => {
            if (ctx.body instanceof Stream) {
                ctx.body.pipe(res);
            } else if (ctx.body) {
                res.statusCode = 200
                res.end(ctx.body)
            } else {
                res.statusCode = 404
                res.end('Not Found')
            }
        })
    }
    // 服务链接成功之后,会触发this.handleRequest
    listen(...args) {
        const server = http.createServer(this.handleRequest);
        server.listen(...args);
    }
    
}

通过this.compose方法就实现了中间件,但是有些地方需要优化一下,比如一个函数多次调用next

app.use(async (ctx, next) => {
    console.log(1)
    await next()
    // 多次调用,应该报错
    await next()
    console.log(2)
})

app.use(async (ctx, next) => {
    console.log(3)
    await next()
    console.log(4)
})

优化之后的this.compose

compose(ctx) {
    let i = -1;
    function next(index) {
        // new
        if(index <= i) return Promise.reject('不能多次调用next')
        index = i
        
        if(index === this.middlewares.length) return Promise.resolve();
        // 传入的第二个参数,用于用户手动执行next函数
        return Promise.resovle(this.middlewares[index](ctx, () => next(index + 1)))
    }

    return next(0)
}

五、总结

分析以及手写完CO和Koa的源码,可以清晰的发现它们都有着“代码嵌套”的思想,它们的不同点在于,CO的递归执行是内部黑盒执行的,而Koa的next方法是通过回调的形式,提供给用户来使用的。

今天分享的“阅读源码”的感悟就分享到这里了,我是一个热衷技术的小前端,github中也有一些积累的小项目,欢迎一起来搞事情

github.com/xinlong-che…