nodejs框架koa,egg以及es6一起学

news2024/11/29 0:36:36

文章目录

    • 前言
    • 问题
    • 关于ES6-Generator
    • egg中的处理
    • Promise
    • node的fs
    • Worksheet Object
    • 参考文档

前言

大家知道,eggjs框架是基于Koa开发的。关于koa,有一张经典的洋葱图:
screenshot

这张图比较形象地展示了koa对于request和response的处理。每一层都留下扩展点,整个框架比较有弹性,方便其它框架来进一步丰富它。比koa传统一点的express框架是用比较典型的web框架思路,req和res来做参数调用中间件的,而koa的不同就是使用了ctx。做为koa的中间件来处理ctx的就是Generator函数。简单解释下,当一个request过来的时候,会依次经过各个中间件进行处理,而处理过程中肯定是需要跳转的,所以跳转需要一个信号或者标识,这个信号就是yield next,当到某个中间件后,该中间件处理完不执行yield next的时候,然后就会反向执行前面那些中间件剩下的逻辑。就像洋葱图里,所有层是被切片的,有点像AOP。举个官方的例子:

var koa = require('koa');
var app = koa();
 
// response-time
app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});
 
// logger
app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});
 
// response
app.use(function *(){
  this.body = 'Hello World';
});
 
app.listen(3000);

其中,我们可以把response-time,logger,response分别作为三个层。不错,这很洋葱。

问题

那么问题来了,如果多层,执行顺序要怎么控制呢?稍微改一下输出,比较方便看结果,代码如下:

var koa = require('koa');
var app = koa();
 
app.use(function* function1(next) {
    console.log('function1 -> before next');
    yield next;
    console.log('function1 -> after next');
});
 
app.use(function* function2(next) {
    console.log('function2 -> before next');
    yield next;
    console.log('function2 -> after next');
});
 
app.use(function* function3(next) {
    console.log('function3 -> before next');
    this.body = 'hello world';
    console.log('function3 -> after next');
});
 
app.listen(3000);

关于这个执行顺序,画个图比较容易看明白:

yield

so 执行结果是这样的:

function1 -> before next
function2 -> before next
function3 -> before next
function3 -> after next
function2 -> after next
function1 -> after next

并不是想象中串行执行function1,2,3的。

关于ES6-Generator

其实这里讲述的顺序有点乱,因为上面算是结果,导致结果的过程是因为我最早光去看代码是不知道function后面的那个星号是什么意思的,去查了才开始了解Generator,于是才有了上面的执行顺序的探索。其实星号和yield可以理解成Generator的特征。看《ECMAScript 6 入门》里Generator这一章就足够详细了,里面有一句话,大概是讲Generator函数可以理解成一个状态机,它封装了多个内部状态。执行Generator函数时会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。回到刚才的栗子,作为状态机Generator对象一定会维护一个状态属性,就是GeneratorStatus。回到刚才的栗子,GeneratorStatus的值从suspended变为closed后,就不会再改变了。也就是说,Generator对象在一个环境中,只能执行一遍,上面的Generator的执行就有点类似于function1(function2(function3({})))

理解了这些,基本上就可以获得了对koa最肤浅的理解。

egg中的处理

再说egg,我理解其实egg是框架的框架,它最大的价值是给开发者定义了规范,做了MVC做的事情。正好这周我也听了不四大神的分享。他说,egg更像java的spring框架。

回到需求本身,举个栗子,做需求的过程中遇到了一个上传的filestream问题。简单来讲,上传文件实际上拿到的是一个fs的read流。如果是文本其实很好办,node也像java一样有类似readline的方法能解决。但是我要解决的是一个多sheet的excel文件。第一反应就是去npm找合适的第三方包来解决。把流行的几个包试了一下,基本都是要读取路径的,没有直接可以对流处理的。其中最强大的xlsx可以处理流,但是直接用是不行的。首先,它读的一定是个正常的文件流,上传来的read流并不支持。又尝试了各种其他方案,一一否决了之后决定先把流生成临时文件,代码如下:

function read(rs) {
  return new Promise((resolve, reject) => {
    rs.on('data', (chunk) => {
      fs.writeFileSync(filePath, chunk, 'binary');
    }).on('end', () => {
      resolve();
    }).on('error', (err) => {
      reject(err);
    });
  });
}

Promise

Promise解决的是异步编程的问题,理念上他像一个容器,里面保存着某个异步操作的结果。从语法上来看,它是一个对象,构造函数包括resolve和rejected。
我理解他也是维护状态来实现回调的,包括三种状态的变化:pending(处理中),resolved(处理成功),reject(处理失败)。resolve是将Promise对象的状态从从Pending变为Resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。同理,reject将Promise对象的状态从从Pending变为Rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。更深入可以看http://liubin.org/promises-book/ 很详细,很强大。

node的fs

用法上API基本足矣,但是要理解的是写文件其实是有一个pipe概念在里面的。就好比两个桶,read流是一个桶(source),用一根管子(pipe)往另一个桶(target)也就是要生成的文件里倒水(data)。用pipe运输data是需要过程的,这个过程就用到了Promise的状态,来看是不是写完了。于是就有了上面的代码。

xlsx–解析Excel文件
有了真实存在的文件而不是一个read流就解决一切了么?当初天真的LZ也是这么一位的,然而真相使用xlsx是只转workbook对象的部分是可以的,主要是下面这一部分。

function parse_xlscfb(cfb, options) {
if(!options) options = {};
fix_read_opts(options);
reset_cp();
var CompObj, Summary, Workbook;
if(cfb.find) {
    CompObj = cfb.find('!CompObj');
    Summary = cfb.find('!SummaryInformation');
    Workbook = cfb.find('/Workbook');
} else {
    prep_blob(cfb, 0);
    Workbook = {content: cfb};
}
 
if(!Workbook) Workbook = cfb.find('/Book');
var CompObjP, SummaryP, WorkbookP;
 
if(CompObj) CompObjP = parse_compobj(CompObj);
if(options.bookProps && !options.bookSheets) WorkbookP = {};
else {
    if(Workbook) WorkbookP = parse_workbook(Workbook.content, options, !!Workbook.find);
    else throw new Error("Cannot find Workbook stream");
}

但是从workbook读取sheet页的对象的时候就抛异常了,把workbook对象打出来看了一下,再对比下解析的源码,大概是因为sheet页的类似于head的一个东西一般是[‘!range’],但是我手里的文件是[‘!ref’],不知道是不是因为背景中叙述的文件来源是xmind导出来的树所导致的。那其实现在是要兼容[‘!ref’]。

Worksheet Object

在xlsx的wiki里看到关于worksheet对象的解释如下:

Each key that does not start with ! maps to a cell (using A-1 notation)
worksheet[address] returns the cell object for the specified address.
Special worksheet keys (accessible as worksheet[key], each starting with !):
ws['!ref']: A-1 based range representing the worksheet range. Functions that work with sheets should use this parameter to determine the range. Cells that are assigned outside of the range are not processed. In particular, when writing a worksheet by hand, be sure to update the range. For a longer discussion, see http://git.io/KIaNKQFunctions that handle worksheets should test for the presence of !ref field. If the !ref is omitted or is not a valid range, functions are free to treat the sheet as empty or attempt to guess the range. The standard utilities that ship with this library treat sheets as empty (for example, the CSV output is an empty string).
When reading a worksheet with the sheetRows property set, the ref parameter will use the restricted range. The original range is set at ws['!fullref']
 
ws['!cols']: array of column properties objects. Column widths are actually stored in files in a normalized manner, measured in terms of the “Maximum Digit Width” (the largest width of the rendered digits 0-9, in pixels). When parsed, the column objects store the pixel width in the wpx field, character width in the wch field, and the maximum digit width in the MDW field.
ws['!merges']: array of range objects corresponding to the merged cells in the worksheet. Plaintext utilities are unaware of merge cells. CSV export will write all cells in the merge range if they exist, so be sure that only the first cell (upper-left) in the range is set.

其实我自己也懒得看英文,上面这段大概就是解释了一下ref和cols两种情况取sheet页里cell范围的相关事宜,具体在查+试并结合xlsx的规范,发现应该是这样把sheet页的cell对象读出来作为一个二维数组返回的:

function _decode_range(range) {
  let o = {s: {c: 0, r: 0}, e: {c: 0, r: 0}};
  let idx, i = 0, cc = 0;
  let len = range.length;
  for (idx = 0; i < len; ++i) {
    if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
    idx = 26 * idx + cc;
  }
  o.s.c = --idx;
 
  for (idx = 0; i < len; ++i) {
    if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
    idx = 10 * idx + cc;
  }
  o.s.r = --idx;
 
  if (i === len || range.charCodeAt(++i) === 58) {
    o.e.c = o.s.c;
    o.e.r = o.s.r;
    return o;
  }
 
  for (idx = 0; i !== len; ++i) {
    if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
    idx = 26 * idx + cc;
  }
  o.e.c = --idx;
 
  for (idx = 0; i !== len; ++i) {
    if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
    idx = 10 * idx + cc;
  }
  o.e.r = --idx;
  return o;
}

至此,我拿到了上传的Excel文件的每个sheet页的每个cell值。后面要做的就是一些业务逻辑处理了,就不粘代码了。整体来看上传大概就是做了下面这几件事:生成临时文件->读取文件->解析数据->得到数据后删除临时文件->对数据进行业务逻辑处理->返回处理后的结果

exports.upload = function*() {
  const rs = yield this.getFileStream();
  let ret = [];
  try {
    yield read(rs).then(function () {
      return _parse_xlsx(filePath);
    }, function (error) {
      return error;
    }).then(function () {
      _clean(filePath);
      ret = _build(data);
    });
    this.body = {
      data: ret,
    };
  } catch (e) {
    this.logger.error(`upload(${data})`, e);
    toResponse(this, Result.error(ResultCode.FILE_NOT_SUPPORT, e.message));
  }
};

参考文档

  • koa的API文档
  • koa官方中文文档
  • egg官方API
  • 阮一峰的ES6入门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/166338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RabbitMQ】高级篇,学习纪录+笔记

目录 一.高级特性 1.1消息的可靠投递 2.1Consumer Ack 3.1消费端限流 4.1TTL 5.1死信队列 6.1延迟队列 7.1日志与监控 7.1.1日志 7.1.2监控 8.1消息追踪 8.1.1Firehose 8.1.2rabbitmq_tracing 9.1消息可靠性保障&#xff08;思路&#xff09; 9.2消息幂等性保障&…

Hash算法,插入排序,希尔排序,选择排序,冒泡排序,归并排序,快速排序,堆排序,基数排序

Hash算法hash就是散列表&#xff0c;就是把任意长度的输入通过散列算法变换成固定长度的输出&#xff0c;该输出就是散列值。实质就是压缩映射&#xff0c;散列值的空间通常远小于输入的空间。常利用hash算法&#xff0c;将输入的一千万个检索串转化为题目中所说大约三百万个不…

《Linux Shell脚本攻略》学习笔记-第七章

7.1 简介 随着磁盘存储技术的发展&#xff0c;最简单地备份方法是添加新的磁盘设备或是使用云存储&#xff0c;而不再是依赖磁盘。 但是也需要压缩备份数据&#xff0c;降低存储空间需求以及传输时间。把数据存放在云端之前应该对其加密。数据在加密之前通常都要先进行归档和压…

2.2总线的性能指标

文章目录一、总线传输周期二、总线时钟周期三、总线工作频率四、总线时钟频率五、总线宽度六、总线带宽1.概念2.注意点3.案例分析4.总结补充七、总线复用八、信号线数九、总结这一节我们一起学习评价 总线性能的指标&#xff0c;这些指标很可能在选择题或者大题第一小题出现。一…

Git团队协作及分支策略

目录 分布式版本控制系统 访问模型 分支策略-Git flow feature 分支策略-Github flow 分支策略-Gitlab flow 主干开发模式 总结 分布式版本控制系统 分布式相比于集中式的最大区别在于开发者可以提交到本地&#xff0c;每个开发者通过克隆&#xff08;git clone&#…

Pytest参数化-下篇

&#x1f60e;&#x1f60e;原文出自&#xff1a;测个der&#xff0c;博主的公众号&#xff0c;格式美观一些。 关于参数化的其他案例 数据嵌套及多参数传参 import pytestpwd_datas [["QINGAN",{"user":"SHIER","pwd":"1234…

5个关键词回顾2022年个推技术实践

作为一家数据智能服务商&#xff0c;2022年每日互动&#xff08;个推&#xff09;在为开发者和行业客户提供优质服务的同时&#xff0c;不断砥砺创新&#xff0c;追逐技术前沿。个推还持续参与开发者生态建设&#xff0c;积极总结、分享自身技术实战经验&#xff0c;面向行业输…

该如何测客户端专项测试?

整个行业现在越来越重视客户端的专项测试了。像接口的性能测试、webview 测试、H5性能分析等&#xff0c;每一项都需要测试。而对于卡顿的分析、系统资源分析、耗电量测试及弱网测试这块&#xff0c;也变得越来越重要了&#xff01;后面也会有相关的文章更新。大家可以戳此关注…

快解析远程访问解决方案——安全稳定,部署简单

我们说的远程办公通常指狭义上的远程办公&#xff0c;是指通过远程技术&#xff0c;或远程控制软件&#xff0c;对远程电脑进行操作办公&#xff0c;实现非本地办公&#xff0c;如在家办公、异地办公、移动办公等远程办公模式。这种技术的关键在于:穿透内网和远程控制的安全性。…

Wisej.NET 3.1.6 Crack

新功能 Wisej.NET 3.1 通过添加几个新的独特功能和简化的安装过程增强了里程碑 3.0 版本。 除了大量错误修复和对我们库的显着性能增强之外&#xff0c;3.1 还包括以下值得注意的新功能&#xff1a; 视觉工作室市场 Wisej.NET 现在比以往任何时候都更容易使用。或 Visual Studi…

联合证券|五大国际巨鳄集体爆雷,美股期指大跳水!

商场现已进入到雷雨季&#xff01; 周五晚间&#xff0c;花旗、摩根大通、高盛、富国和贝莱德团体爆雷。 花旗集团上一年第四季度每股收益低于预期&#xff0c;尽管营收好于预期。花旗集团Q4每股收益1.16美元&#xff0c;预期为1.17美元。财报发布之后一度暴降超3%&#xff1b;…

【机器学习之模型融合】Stacking堆叠法

目录 1、Stacking的基本思想&#x1f48d; 2、思考&#x1f48e; 3、在sklearn中实现Stacking&#x1f3af; 3.1、导入工具库和数据&#x1f455; 3.2、定义交叉验证评估函数&#x1f457; 3.3、个体学习器与元学习器的定义&#x1f357; 3.4、模型构建&#x1f36a; 4…

【Linux】创建新用户 sudo配置,添加信任

目录 一、创建新用户 二、sudo不被允许 三、添加信任用户 一、创建新用户 相关指令&#xff1a; adduser [用户名]&#xff1a;创建新用户 passwd [用户名]&#xff1a;修改用户密码 su [用户名]&#xff1a;切换到该用户 设置密码&#xff0c;重复输入两遍之后&#xff0…

大数据技术架构(组件)——Hive:环境准备1

1.0.1、环境准备1.0.1.0、maven安装1.0.1.0.1、下载软件包1.0.1.0.2、配置环境变量1.0.1.0.3、调整maven仓库打开$MAVEN_HOME/conf/settings.xml文件&#xff0c;调整maven仓库地址以及镜像地址<settings xmIns"http://maven.apache.org/SETTINGS/1.0.0"xmIns:xsi…

电脑磁盘占用率高怎么办?

Windows磁盘占用率高是一种普遍存在的问题&#xff0c;相信很多用户遇到过不止一次&#xff0c;它可能是在刚开机时、可能是在下载文件时、也可能是在开启关闭应用程序时……当磁盘占用高之后&#xff0c;您的计算机运行速度会变得像蜗牛一样缓慢&#xff0c;更糟糕的是有些电脑…

python 代码注释

文章目录写在前面使用方法plainEpytextGoogleNumpyreStructuredText相关程序包其他写在前面 如果说高效率的算法是一个项目的内核&#xff0c;那么完备的文档注释、API 接口则是项目的外壳&#xff0c;直接与客户交互。 pycharm 提供了 5 种 代码注释格式。 分别是 plain, epy…

Tslib配置文件ts.conf

默认&#xff1a; # Access plugins ################# Uncomment if you wish to use the linux input layer event interface module_raw input# For other driver modules, see the ts.conf man page# Filter plugins ################# Uncomment if first or last sample…

【运维心得】正确的校正mysql-slave及mysqldump

实践出真知&#xff0c;有些细节&#xff0c;记录下。本文不涉及主备知识&#xff0c;有需右转: https://blog.csdn.net/qq_26834611/article/details/121385550mysql 正确的dump 命令:0. 检查当前主库所在位置ip addr 查看虚拟ip所在位置 1. 备机数据库dump备份:mysqldump -ur…

【ChatGPT】注册OpenAI账号试用ChatGPT

主要参考&#xff1a; 手把手教你注册 AI 聊天机器人模型 ChatGPT&#xff1a;大体流程注册OpenAI账号试用ChatGPT指南&#xff1a; 相关网站 openai: https://beta.openai.com/signup直接登录charGPT&#xff1a;https://chat.openai.com/接入微信&#xff0c;1.2K&#xf…

OneKeyGhost工具进行备份还原windows操作系统

OneKeyGhost OneKey Ghost是一款设计专业、操作简便的绿色程序&#xff0c;能够在 Windows 下对任意分区进行一键备份、恢复&#xff0c;支持ISO文件、光盘、U盘里的GHO文件硬盘安装。 OneKeyGhost备份wind系统 系统需要包含两个磁盘&#xff0c;一个系统盘另外一个数据盘。 …