2024年最全学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理(3),2024年最新面试基本问题及答案

算法

  1. 冒泡排序

  2. 选择排序

  3. 快速排序

  4. 二叉树查找: 最大值、最小值、固定值

  5. 二叉树遍历

  6. 二叉树的最大深度

  7. 给予链表中的任一节点,把它删除掉

  8. 链表倒叙

  9. 如何判断一个单链表有环

由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

compose函数,传入一个数组,返回一个函数。对入参是不是数组和校验数组每一项是不是函数。

function compose (middleware) {

if (!Array.isArray(middleware)) throw new TypeError(‘Middleware stack must be an array!’)

for (const fn of middleware) {

if (typeof fn !== ‘function’) throw new TypeError(‘Middleware must be composed of functions!’)

}

// 传入对象 context 返回Promise

return function (context, next) {

// last called middleware #

let index = -1

return dispatch(0)

function dispatch (i) {

if (i <= index) return Promise.reject(new Error(‘next() called multiple times’))

index = i

let fn = middleware[i]

if (i === middleware.length) fn = next

if (!fn) return Promise.resolve()

try {

return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));

} catch (err) {

return Promise.reject(err)

}

}

}

}

把简化的代码和koa-compose代码写在了一个文件中。koa/examples/simpleKoa/koa-compose.js

hs koa/examples/

然后可以打开localhost:8080/simpleKoa,开心的把代码调试起来

不过这样好像还是有点麻烦,我还把这些代码放在codepenhttps://codepen.io/lxchuan12/pen/wvarPEb中,直接可以在线调试啦。是不是觉得很贴心_,自己多调试几遍便于消化理解。

你会发现compose就是类似这样的结构(移除一些判断)。

// 这样就可能更好理解了。

// simpleKoaCompose

const [fn1, fn2, fn3] = this.middleware;

const fnMiddleware = function(context){

return Promise.resolve(

fn1(context, function next(){

return Promise.resolve(

fn2(context, function next(){

return Promise.resolve(

fn3(context, function next(){

return Promise.resolve();

})

)

})

)

})

);

};

fnMiddleware(ctx).then(handleResponse).catch(onerror);

也就是说koa-compose返回的是一个PromisePromise中取出第一个函数(app.use添加的中间件),传入context和第一个next函数来执行。

第一个next函数里也是返回的是一个PromisePromise中取出第二个函数(app.use添加的中间件),传入context和第二个next函数来执行。

第二个next函数里也是返回的是一个PromisePromise中取出第三个函数(app.use添加的中间件),传入context和第三个next函数来执行。

第三个…

以此类推。最后一个中间件中有调用next函数,则返回Promise.resolve。如果没有,则不执行next函数。这样就把所有中间件串联起来了。这也就是我们常说的洋葱模型。

不得不说非常惊艳,“玩还是大神会玩”

这种把函数存储下来的方式,在很多源码中都有看到。比如lodash源码的惰性求值,vuex也是把action等函数存储下,最后才去调用。

搞懂了koa-compose 洋葱模型实现的代码,其他代码就不在话下了。

错误处理


中文文档 错误处理

仔细看文档,文档中写了三种捕获错误的方式。

  • ctx.onerror 中间件中的错误捕获

  • app.on('error', (err) => {}) 最外层实例事件监听形式 也可以看看例子koajs/examples/errors/app.js 文件

  • app.onerror = (err) => {} 重写onerror自定义形式 也可以看测试用例 onerror

// application.js 文件

class Application extends Emitter {

// 代码有简化组合

listen(){

const fnMiddleware = compose(this.middleware);

if (!this.listenerCount(‘error’)) this.on(‘error’, this.onerror);

const onerror = err => ctx.onerror(err);

fnMiddleware(ctx).then(handleResponse).catch(onerror);

}

onerror(err) {

// 代码省略

// …

}

}

ctx.onerror

lib/context.js文件中,有一个函数onerror,而且有这么一行代码this.app.emit('error', err, this)

module.exports = {

onerror(){

// delegate

// app 是在new Koa() 实例

this.app.emit(‘error’, err, this);

}

}

app.use(async (ctx, next) => {

try {

await next();

} catch (err) {

err.status = err.statusCode || err.status || 500;

throw err;

}

});

try catch 错误或被fnMiddleware(ctx).then(handleResponse).catch(onerror);,这里的onerrorctx.onerror

ctx.onerror函数中又调用了this.app.emit('error', err, this),所以在最外围app.on('error',err => {})可以捕获中间件链中的错误。因为koa继承自events模块,所以有’emit’和on等方法)

koa2 和 koa1 的简单对比


中文文档中描述了 koa2 和 koa1 的区别

koa1中主要是generator函数。koa2中会自动转换generator函数。

// Koa 将转换

app.use(function *(next) {

const start = Date.now();

yield next;

const ms = Date.now() - start;

console.log(${this.method} ${this.url} - ${ms}ms);

});

koa-convert 源码

vscode/launch.json文件,找到这个program字段,修改为"program": "${workspaceFolder}/koa/examples/koa-convert/app.js"

通过F5启动调试(直接跳到下一个断点处)F10单步跳过F11单步调试调试走一遍流程。重要地方断点调试。

app.use时有一层判断,是否是generator函数,如果是则用koa-convert暴露的方法convert来转换重新赋值,再存入middleware,后续再使用。

class Koa extends Emitter{

use(fn) {

if (typeof fn !== ‘function’) throw new TypeError(‘middleware must be a function!’);

if (isGeneratorFunction(fn)) {

deprecate('Support for generators will be removed in v3. ’ +

'See the documentation for examples of how to convert old middleware ’ +

‘https://github.com/koajs/koa/blob/master/docs/migration.md’);

fn = convert(fn);

}

debug(‘use %s’, fn._name || fn.name || ‘-’);

this.middleware.push(fn);

return this;

}

}

koa-convert源码挺多,核心代码其实是这样的。

function convert(){

return function (ctx, next) {

return co.call(ctx, mw.call(ctx, createGenerator(next)))

}

function * createGenerator (next) {

return yield next()

}

}

最后还是通过co来转换的。所以接下来看co的源码。

co 源码

tj大神写的co 仓库

本小节的示例代码都在这个文件夹koa/examples/co-generator中,hs koa/example,可以自行打开https://localhost:8080/co-generator调试查看。

co源码前,先看几段简单代码。

// 写一个请求简版请求

function request(ms= 1000) {

return new Promise((resolve) => {

setTimeout(() => {

resolve({name: ‘若川’});

}, ms);

});

}

// 获取generator的值

function* generatorFunc(){

const res = yield request();

console.log(res, ‘generatorFunc-res’);

}

generatorFunc(); // 报告,我不会输出你想要的结果的

简单来说co,就是把generator自动执行,再返回一个promisegenerator函数这玩意它不自动执行呀,还要一步步调用next(),也就是叫它走一步才走一步

所以有了async、await函数。

// await 函数 自动执行

async function asyncFunc(){

const res = await request();

console.log(res, ‘asyncFunc-res await 函数 自动执行’);

}

asyncFunc(); // 输出结果

也就是说co需要做的事情,是让generatorasync、await函数一样自动执行。

模拟实现简版 co(第一版)

这时,我们来模拟实现第一版的co。根据generator的特性,其实容易写出如下代码。

// 获取generator的值

function* generatorFunc(){

const res = yield request();

console.log(res, ‘generatorFunc-res’);

}

function coSimple(gen){

gen = gen();

console.log(gen, ‘gen’);

const ret = gen.next();

const promise = ret.value;

promise.then(res => {

gen.next(res);

});

}

coSimple(generatorFunc);

// 输出了想要的结果

// {name: “若川”}“generatorFunc-res”

模拟实现简版 co(第二版)

但是实际上,不会上面那么简单的。有可能是多个yield和传参数的情况。传参可以通过这如下两行代码来解决。

const args = Array.prototype.slice.call(arguments, 1);

gen = gen.apply(ctx, args);

两个yield,我大不了重新调用一下promise.then,搞定。

// 多个yeild,传参情况

function* generatorFunc(suffix = ‘’){

const res = yield request();

console.log(res, ‘generatorFunc-res’ + suffix);

const res2 = yield request();

console.log(res2, ‘generatorFunc-res-2’ + suffix);

}

function coSimple(gen){

const ctx = this;

const args = Array.prototype.slice.call(arguments, 1);

gen = gen.apply(ctx, args);

console.log(gen, ‘gen’);

const ret = gen.next();

const promise = ret.value;

promise.then(res => {

const ret = gen.next(res);

const promise = ret.value;

promise.then(res => {

gen.next(res);

});

});

}

coSimple(generatorFunc, ’ 哎呀,我真的是后缀’);

模拟实现简版 co(第三版)

问题是肯定不止两次,无限次的yield的呢,这时肯定要把重复的封装起来。而且返回是promise,这就实现了如下版本的代码。

function* generatorFunc(suffix = ‘’){

const res = yield request();

console.log(res, ‘generatorFunc-res’ + suffix);

const res2 = yield request();

console.log(res2, ‘generatorFunc-res-2’ + suffix);

const res3 = yield request();

console.log(res3, ‘generatorFunc-res-3’ + suffix);

const res4 = yield request();

console.log(res4, ‘generatorFunc-res-4’ + suffix);

}

function coSimple(gen){

const ctx = this;

const args = Array.prototype.slice.call(arguments, 1);

gen = gen.apply(ctx, args);

console.log(gen, ‘gen’);

return new Promise((resolve, reject) => {

onFulfilled();

function onFulfilled(res){

const ret = gen.next(res);

next(ret);

}

function next(ret) {

const promise = ret.value;

promise && promise.then(onFulfilled);

}

});

}

coSimple(generatorFunc, ’ 哎呀,我真的是后缀’);

但第三版的模拟实现简版co中,还没有考虑报错和一些参数合法的情况。

最终来看下co源码

这时来看看co的源码,报错和错误的情况,错误时调用reject,是不是就好理解了一些呢。

function co(gen) {

var ctx = this;

var args = slice.call(arguments, 1)

// we wrap everything in a promise to avoid promise chaining,

// which leads to memory leak errors.

// see https://github.com/tj/co/issues/180

return new Promise(function(resolve, reject) {

// 把参数传递给gen函数并执行

if (typeof gen === ‘function’) gen = gen.apply(ctx, args);

// 如果不是函数 直接返回

if (!gen || typeof gen.next !== ‘function’) return resolve(gen);

onFulfilled();

/**

  • @param {Mixed} res

  • @return {Promise}

  • @api private

*/

function onFulfilled(res) {

var ret;

try {

ret = gen.next(res);

} catch (e) {

return reject(e);

}

next(ret);

}

/**

  • @param {Error} err

  • @return {Promise}

  • @api private

*/

function onRejected(err) {

var ret;

try {

ret = gen.throw(err);

} catch (e) {

return reject(e);

}

next(ret);

}

/**

  • Get the next value in the generator,

  • return a promise.

  • @param {Object} ret

  • @return {Promise}

  • @api private

*/

// 反复执行调用自己

function next(ret) {

// 检查当前是否为 Generator 函数的最后一步,如果是就返回

if (ret.done) return resolve(ret.value);

// 确保返回值是promise对象。

var value = toPromise.call(ctx, ret.value);

// 使用 then 方法,为返回值加上回调函数,然后通过 onFulfilled 函数再次调用 next 函数。

if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

// 在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为 rejected,从而终止执行。

return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ’

  • ‘but the following object was passed: "’ + String(ret.value) + ‘"’));

}

});

}

koa 和 express 简单对比


中文文档 koa 和 express 对比

文档里写的挺全面的。简单来说koa2语法更先进,更容易深度定制(egg.jsthink.js、底层框架都是koa)。

总结

文章通过授人予鱼不如授人予鱼的方式,告知如何调试源码,看完了koa-compose洋葱模型实现,koa-convertco等源码。

koa-compose是将app.use添加到middleware数组中的中间件(函数),通过使用Promise串联起来,next()返回的是一个promise

koa-convert 判断app.use传入的函数是否是generator函数,如果是则用koa-convert来转换,最终还是调用的co来转换。

co源码实现原理:其实就是通过不断的调用generator函数的next()函数,来达到自动执行generator函数的效果(类似async、await函数的自动自行)。

koa框架总结:主要就是四个核心概念,洋葱模型(把中间件串联起来),http请求上下文(context)、http请求对象、http响应对象。

本文仓库在这里若川的 koa-analysis github 仓库 https://github.com/lxchuan12/koa-analysis。求个star呀。

git clone https://github.com/lxchuan12/koa-analysis.git

再强烈建议下按照本文阅读最佳方式,克隆代码下来,动手调试代码学习更加深刻

如果读者发现有不妥或可改善之处,再或者哪里没写明白的地方,欢迎评论指出,也欢迎加我微信交流lxchuan12。另外觉得写得不错,对您有些许帮助,可以点赞、评论、转发分享,也是对笔者的一种支持,万分感谢。

解答下开头的提问

仅供参考

1、koa洋葱模型怎么实现的。

可以参考上文整理的简版koa-compose作答。

// 这样就可能更好理解了。

// simpleKoaCompose

const [fn1, fn2, fn3] = this.middleware;

const fnMiddleware = function(context){

return Promise.resolve(

fn1(context, function next(){

return Promise.resolve(

fn2(context, function next(){

return Promise.resolve(

fn3(context, function next(){

return Promise.resolve();

})

)

})

)

})

);

分享

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

koa-analysis。求个star呀。

git clone https://github.com/lxchuan12/koa-analysis.git

再强烈建议下按照本文阅读最佳方式,克隆代码下来,动手调试代码学习更加深刻

如果读者发现有不妥或可改善之处,再或者哪里没写明白的地方,欢迎评论指出,也欢迎加我微信交流lxchuan12。另外觉得写得不错,对您有些许帮助,可以点赞、评论、转发分享,也是对笔者的一种支持,万分感谢。

解答下开头的提问

仅供参考

1、koa洋葱模型怎么实现的。

可以参考上文整理的简版koa-compose作答。

// 这样就可能更好理解了。

// simpleKoaCompose

const [fn1, fn2, fn3] = this.middleware;

const fnMiddleware = function(context){

return Promise.resolve(

fn1(context, function next(){

return Promise.resolve(

fn2(context, function next(){

return Promise.resolve(

fn3(context, function next(){

return Promise.resolve();

})

)

})

)

})

);

分享

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值