一、前言

大家可能都知道koa是express核心原班人马写的,那么他们为什么要在express后再造一个koa的轮子呢? 今天就给大家带来一些分析。希望能够起到一个抛砖引玉的作用。

其实,这个题目也可以这么问, express有什么缺点? koa解决了一些express的什么问题? 这也在一些面试题中会这么问。所以,为了实现自己的理想(money), 志同道合的同志们可以随我分析一下了。

我想先从express的一个非常重要的特征开始说起,那就是 中间件。 中间件贯穿了express的始终,我们在express中比较常用到应用级的中间件,比如:

    const app = require('express')();
    app.use((req, res, next) => {
        // 做一些事情。。。
        next();
    })

再比如我们更常用到的路由级中间件。 我为什么要叫它是路由级的呢? 因为它的内部也同样维护着一个next

    app.get('/', (req, res, next) => {
        res.send('something content');
    })

这里中间件我不详细展开。 后面有我对中间件的详细解析,欢迎大家围观。

那么我们可以看到,其中会有个关键的next, 它在express内部做的是从栈中获取下一个中间件的关键。

那么重点来了, 我们开始研究express这里的实现会隐藏什么问题。

二、中间件问题解析

通过一个例子来看:

    const Express = require('express');
    const app = new Express();
    const sleep = () => new Promise(resolve => setTimeout(function(){resolve(1)}, 2000));
    const port = 8210;
    function f1(req, res, next) {
      console.log('this is function f1....');
      next();
      console.log('f1 fn executed done');
    }
    
    function f2(req, res, next) {
      console.log('this is function f2....');
      next();
      console.log('f2 fn executed done');
    }
    
    async function f3(req, res) {
      console.log('f3 send to client');
      res.send('Send To Client Done');
    }
    app.use(f1);
    app.use(f2);
    app.use(f3);
    app.get('/', f3)
    app.listen(port, () => console.log(`Example app listening on port ${port}!`))

理想下的返回,和真正的返回,目前是没有问题的。

    this is function f1....
    this is function f2....
    f3 send to client
    f1 fn executed done
    f2 fn executed done

好的,那么再继续下一个例子。 在下一个例子中,其它都是没有变化的,只有一个地方:

    const sleep = () => new Promise(resolve => setTimeout(function(){resolve()}, 1000))
    async function f3(req, res) {
        await sleep();
      console.log('f3 send to client');
      res.send('Send To Client Done');
    }

这时你认为的返回值顺序是什么样的呢?

可能会认为跟上面的没有变化,因为我们增加await了,照道理应该等待await执行完了,再去执行下面的代码。 其实结果并不是。

返回的结果是:

    this is function f1....
    this is function f2....
    f1 fn executed done
    f2 fn executed done
    f3 send to client

发生了什么?? 大家可能有点吃惊。但是,如果深入到express的源码中去一探究竟,问题原因也就显而易见了。

具体源码我在这一篇中就不详细分析了,直接说出结论:

因为express中的中间件调用不是Promise 所以就算我们加了async await 也不管用。

那么koa中是怎么使用的呢?

const Koa = require('koa');
const app = new Koa();
const sleep = () => new Promise(resolve => setTimeout(function(){resolve()}, 1000))
app.use(async (ctx, next) => {
    console.log('middleware 1 start');
    await next();
    console.log('middleware 1 end');
});
app.use(async (ctx, next) => {
    await sleep();
    console.log('middleware 2 start');
    await next();
    console.log('middleware 2 end');
});
app.use(async (ctx, next) => {
    console.log('middleware 3 start')
    ctx.body = 'test middleware executed';
})

不出所料, 实现的顺序是:

middleware 1 start
middleware 2 start
middleware 3 start
middleware 2 end
middleware 1 end

原因是: koa 内部使用了Promise,所以能够控制顺序的执行。

综合上面的例子,我们知道了express中中间件使用的时候,如果不清楚原理,是容易踩坑的。 而koa通过使用async 和 await next() 实现洋葱模型,即:通过next,到下一个中间件,只要下面的中间件执行完成后,才一层层的再执行上面的中间件,直到全部完成。

三、错误逻辑捕获

3.1 express的错误捕获逻辑

同样,先看express在错误逻辑的捕获上有什么特点:

app.use((req, res, next) => {
    // c 没有定义
    const a = c;
});
// 错误处理中间件
app.use((err, req, res, next) => {
    if(error) {
        console.log(err.message);
    }
    next()
})
process.on("uncaughtException", (err) => {
    console.log("uncaughtException message is::", err);
})

再看一个异步的处理:

app.use((req, res, next) => {
    // c 没有定义
    try {
        setTimeout(() => {
            const a = c;
            next()
        }, 0)
    } catch(e) {
        console.log('异步错误,能catch到么??')
    }
});
app.use((err, req, res, next) => {
    if(error) {
        console.log('这里会执行么??', err.message);
    }
    next()
})
process.on("uncaughtException", (err) => {
    console.log("uncaughtException message is::", err);
})

可以先猜一下同步和异步的会不会有所区别?

答案是: 有很大的区别!!

具体分开来看:

  • 同步的时候, 不会触发 uncaughtException, 而进入了错误处理的中间件。
  • 异步的时候,不会触发错误处理中间件, 而会触发 uncaughtException

这中间发生了什么?

3.2 同步逻辑错误获取的底层逻辑

逻辑是: express内部对同步发生的错误进行了拦截,所以,不会传到负责兜底的node事件 uncaughtException ,如果发生了错误,则直接绕过其它中间件,进入错误处理中间件。 那么,这里会有一个很容易被忽略的点, 那就是,即使没有错误处理中间件做兜底,也不会进入node的 uncaughtException, 这时, 会直接报 500错误。

3.3 异步逻辑错误获取的底层逻辑

还是因为express的实现并没有把Promise考虑进去, 它的中间件执行是同步顺序执行的。 所以如果有异步的,那么错误处理中间件实际是兜不住的,所以,express对这种中间件中的异步处理错误无能为力。

从上面的异步触发例子来看, 除了错误处理中间件没有触发,我们当中的try catch也没有触发。这是一个大家可能都会踩到的坑。 这里其实是与javascript的运行机制相关了。具体原因见本篇 JavaScript异步队列进行try catch时的问题解决

所以要想去catch 当前的错误,那么就需要用 async await

app.use(async (req, res, next) => {
    try {
        await (() => new Promise((resolve, reject) => {
            http.get('http://www.example.com/testapi/123', res => {
                reject('假设错误了');
            }).on('error', (e) => {
                throw new Error(e);
            })
        }))();
    } catch(e) {
        console.log('异步错误,能catch到么??')
    }
});

这样,我们的catch不仅可以获取到, uncaughtException也可以获取到。

3.4 koa的错误获取逻辑

总体上是跟express差不多,因为js的底层处理还是一致的。但还是使用上有所差异。

上面也提过洋葱模型,特点是最开始的中间件,在最后才执行完毕,所以,在koa上,可以把错误处理中间件放到中间件逻辑最前面。

const http = require('http');
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next)=>{
    try {
        await next();
    } catch (error) {
        // 响应用户
        ctx.status = 500;
        ctx.body = '进入默认错误中间件';
        // ctx.app.emit('error', error); // 触发应用层级错误事件
    }
});
app.use(async (ctx, next) => {
    await (() => new Promise((resolve, reject) => {
        http.get('http://www.example.com/testapi/123', res => {
            reject('假设错误了');
        }).on('error', (e) => {
            throw new Error(e);
        })
    }))();
    await next();
})

上面的代码, reject出的错误信息,会被最上面的错误处理中间件捕获。总结来说,js的底层机制是一样的, 只是使用方法和细节点上不一样,大家在用的时候注意一下,

到此这篇关于node.js express和koa中间件机制和错误处理机制的文章就介绍到这了,更多相关node.js express和koa内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

node.js express和koa中间件机制和错误处理机制的更多相关文章

  1. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  3. Node.js调试技术总结分享

    Node.js是一个可以快速构建网络服务及应用的平台。该平台的构建是基于Chrome's JavaScript runtime,也就是说,实际上它是对Google V8引擎(应用于Google Chrome浏览器)进行了封装。 今天介绍Node.js调式目前有几种技术,需要的朋友可以参考下。

  4. node.js实现http服务器与浏览器之间的内容缓存操作示例

    这篇文章主要介绍了node.js实现http服务器与浏览器之间的内容缓存操作,结合实例形式分析了node.js http服务器与浏览器之间的内容缓存原理与具体实现技巧,需要的朋友可以参考下

  5. 教你如何使用node.js制作代理服务器

    本文介绍了如何使用node.js制作代理服务器,图文并茂,十分的详细,代码很简洁易懂,这里推荐给大家。

  6. Koa项目搭建过程详细记录

    本篇文章主要介绍了Koa项目搭建过程详细记录,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. node.js中的fs.openSync方法使用说明

    这篇文章主要介绍了node.js中的fs.openSync方法使用说明,本文介绍了fs.openSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  8. Node.js+ELK日志规范的实现

    这篇文章主要介绍了Node.js+ELK日志规范的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. node.js爬虫框架node-crawler初体验

    这篇文章主要介绍了node.js爬虫框架node-crawler的相关资料,帮助大家利用node.js进行爬虫,感兴趣的朋友可以了解下

  10. node.js中的fs.existsSync方法使用说明

    这篇文章主要介绍了node.js中的fs.existsSync方法使用说明,本文介绍了fs.existsSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部