异步async/同步await

image

更正下同步和异步的话述. 容易出现混淆.


async 是异步. await是等待/同步, 异步为并行任务, 同步同步的是执行顺序, 但是node的事件池机制较特殊. promise执行过程为异步, 但理解上和写法上, 变成了同步.

日常口语上经常说同步, 导致async和理解上产生歧异. 实际async目的是异步. 但是习惯性加await, 效果同步. 导致习惯反应上, 将async当成了同步来理解. 不加的情况下,就是异步。

即:

同步=async+await

异步=async

nodejs global的妙用

//gctx.js

//gctx.js
/**
 * global context
 * @type {{}}
 */
let gctx = {};
gctx.error = function(msg, code) {
  let error = (new Error);
  error.code = code;
  error.message = msg;
  error.file = error.stack.split('\n')[2];
  return error;
};
module.exports = gctx;

//subcall.js

let subcall = {};
subcall.call = function() {
  //调用全局报错
  //减少重复性的引用
  throw gctx.error('test', 500);
};
module.exports = subcall;

//index.js

const gctx = require('./gctx.js');
const subcall = require('./subcall.js');
//引入全局global context
global.gctx = gctx;

try {
  // throw gctx.error('test', 500);
  subcall.call();
} catch (e) {
  console.log(e.message, e.code, e.file);
}

输出:

test 500     at Object.subcall.call (....\local_test\subcall.js:4:14)

 

nodejs 优雅的抛出异常

//ctx.js
let ctx = {};
ctx.error = function (msg, code) {
    let error = (new Error);
    error.code = code;
    error.message = msg;
    error.file = error.stack.split('\n')[2];
    return error;
}
module.exports = ctx;
//test.js
const ctx = require('./ctx.js');

try {
    throw ctx.error("test", 500);
} catch (e) {
    console.log(e.message, e.code, e.file);
}
test 500 at Object.<anonymous> (F:\jy\jkpt_backend\local_test\t_error.js:9:20)

 

eggjs-框架, 最近入了nodejs的坑…写给新东家的新框架,基于eggjs

 框架结构

  • app[框架主目录]
    • controller[控制器]
      控制器已实现路由的自动装载,只需要建立具体的控制器. 具体功能业务按需建立目录. 不要将不相干的业务揉在同一模块,同一控制器里.

      • admin[业务模块]
        • admin.js[控制器]
        • system.js[控制器]
    • extend[扩展]
      扩展目录中,可以给控制器,请求类,上下文扩展通用函数(https://eggjs.org/zh-cn/basics/extend.html)
    • io[socket控制器]
      特别注意,静态资源,控制器请求以及io请求,都是同一个端口.通过特殊解析实现具体访问.

      • controller[socket控制器]
        • client[socket模块]
          保留模块, 后续新socket编写需要单独建立模块目录,并在具体动作中实现业务逻辑. 同样在egg的基础上补充了自动路由装载的实现.

          • client.js[控制器]
            这个socket控制器为保留控制器, 为兼容原客户端的socket请求以及回调的特殊处理类. 所有原socket请求都会通过映射, 跳转到/app/service/client/*里的业务类进行特殊处理,并回调.原请求方式. io.emit(‘message’,{api:’login’,param:{username:’xxx’,password:’xxx’}},function(res){})新请求方式. io.emit(‘/admin/admin/login​​’,{param:{username:’xxx’,password:’xxx’}},function(res){})需要明确注意, socket本质上是不存在回调的. 只是socket.io-client内部和服务器实现了packetid为标志的特殊实现. 其它任何语言都没有…但然. 通过原理编写也行..原理:https://www.cnblogs.com/lightsong/p/10226940.html​​
      • extend[socket扩展]
        同/app/extend
      • middleware[socket中间件]
        • connection.js[连接中间件]
          建立链接时, 可以做权限校验
        • packet.js[数据包中间件]
          数据请求过程中触发
    • lib[工具类]
      引用方式按最原始的方式引用. 不封装引用的主要目的是保留代码提示.

      • common[通用工具目录]
        • commonHelper.js[原通用类 ]
          按需要加,不需要的不要乱加进来,明确功能的.建立目录单独存放​
        • expressHelper.js[快递通用类]
          快递一些通用的获取调用, 在各自模块中实现, 并提供统一调用. 通用类,根据类型去调用具体的帮助类, 减少过程式代码, 增加代码可读性
      • cngg[功能模块目录]
        不同于业务, 一般功能模块都有其特殊实现和封装. 单独放这里. 业务功能再在业务目录里实现. 最大限度避免代码混乱. 当扩展达到一定的重复性和可用性, 可以考虑写成插件

        • cnggHelper.js[菜鸟裹裹帮助类]
      • kdn[快递鸟目录]
    • middleware[中间件]
      个人建议称为中间处理件会相对好理解, 需要先了解下洋葱模型(https://eggjs.org/zh-cn/basics/middleware.html), 类似过滤器

      • commonRequest.js[通用请求中间件]
        将常规请求进行前置处理, 如get和body合并到ctx.request.params中. 不区分get和post.
      • mwAdminUser.js[用户请求权限中间件]
        实现用户的权限判断和拦截
    • model[数据模型]
      暂时使用的数据库是mongodb, 然后暂时选用的访问方式为mongose(https://mongoosejs.com/docs/guide.html), 同时提供原访问方式, 方便快速复用原代码(尽量能写成新的写成新的…别只复制粘贴…)

      • Admin[业务模块]
        特别注意,其它目录都是小写开头, 只有数据模型的目录和类是大写开头….

        • Admin.js[数据模型类]
          数据层可复用代码,尽量复用到数据模型. 业务层复用业务模型. 尽量减少重复代码. 如可以写一个函数load(id), 找不到直接报错. 后续通用调用即可.
    • public[静态文件]
      egg-static提供静态文件访问支持​, 同端口访问时. 如果路径为/public/则会自动进入该目录​, 客户端和小访问量服务器可以直接使用. 大访问量, 需要实际压力测试看看. 最好还是走nginx做反向代理.

      • assets[静态资源目录]
      • backstage[后端静态资源目录,主要是组件类封装]
        复制原资源文件, 暂时按习惯的来
      • upload[上传文件目录]
    • router[路由配置]
      • router.dev.js
        路由实际配置脚本, 自动化脚本能满足基本路由情况, 如果需要特殊路由或者兼容原路由. 可以单独在这个地方再补充映射https://eggjs.org/zh-cn/basics/router.html
    • schedule[计划任务]
      计划任务只需要复制即可使用, 但是发现需要重启服务才会引入, 需要研究下热部署?
    • service[业务]
      需要理解下新的设计分层, 该形式接近MVC, 但是不是MVC.常规MVC是模型,视图,控制器, egg 结构层是 数据层(model), 控制器(controller), 业务(service), 视图(view), 控制器只做权限以及参数校验, 具体功能封装到业务层中. ​主要目的是实现代码的高复用性.如登陆,封装成service.user.login(username,password), 后续任何地方都是调用这个业务, 即可实现登陆. ​同时后续的单元测试, 主要是测试业务类.同时, 业务需要以抛出异常的形式代替返回值判断, 并再最外层进行异常处理. 可大大增加代码的可读写以及编写. 避免没必要的各种判断​

      • admin[业务模块]
        • adminService.js[业务类]
          结构类似控制器, 全是只为处理具体业务代码
      • client[保留模块]
        原客户端的io调用使用的方式和egg-socket不一样. 未避免冲突, 单独配置一个io路由,并将原请求动作全部映射到这里的具体业务动作. 同时兼容了socket.io-client的回调

        • enterpriseService.js[原客户端所有socket调用]
    • view[视图]
      所有页面文件以*.ejs做后缀, webstorm,vscode等格式化和开发过程中<% %>会有代码提示, 会极大的提高开发效率, 测试过程中, 也可以考虑页面代码在<%%>中测试再贴回去. 页面保存是实时更新的. nodejs其它功能模块修改在ssd的情况下, 依然需要4,5 秒的重启

      • admin[模块目录]
        • site[控制器目录]
          • login.ejs[登陆页面]
            页面对应具体的控制器
      • layout[通用页面组件]
        • layout.ejs[图层]
          包含常用的静态资源文件引入, 控制器里渲染页面时, 图层会包裹具体的页面代码, 可以方便全局性调整. 对于可复用的页面组件, 都可以考虑封装成*.ejs或者作成vue组件, 按需要来
      • 200.ejs[通用消息提示页面]
      • 500.ejs[通用错误消息提示页面]
    • router.js[路由脚本]
      用于引用/app/router里的实际路由脚本​, 路由脚本在egg的基础上加了自动配置, 只需要复制或新增控制器, 就会自动添加路由
  • test[单元测试目录]
  • local_test[自己编写的框架外的测试脚本]
  • cache[缓存目录]
    如果使用缓存, 则目标缓存会生成到该路径下, 如果服务器有多节点. 缓存必需存放到redis或数据库, 不能配置单机的fs或内存缓存. 除非是代码或页面类缓存,视具体情况选择
  • config[配置目录]
    • locale[翻译目录]
      • zh-CN.js[翻译语言配置脚本]
        egg-validate的翻译,需要在这个地方进行配置
    • config.default.js[默认配置]
    • config.dev.js[具体环境配置脚本]
      可根据启动命令不同,加载不同配置文件,方便测试
    • plugin.js[插件配置]
      框架使用插件,必须在这个地方启用, 然后如果有特殊参数配置,在/config/config.default.js里进行详细参数配置. egg默认启用的插件需要到/node_modules/eggjs 里进行查看.
  • logs[日志目录]
    • test_egg[项目名]
      • common-error.log[常规日志]
      • egg-agent.log[agent日志]
      • egg-schedule.log[计划任务日志]
      • egg-web.log[egg内部日志]
  • package.json[配置文件]
    • 启动命令
      • 启动开发环境:npm run dev
        • 实际脚本:egg-bin dev –sticky –port 7001
      • 启动生产环境:npm run start
        • 实际脚本:egg-scripts start –daemon –sticky –port 7001
    • 启动模式
      • 框架是以 Cluster 方式启动的,而 socket.io 协议实现需要 sticky 特性支持,否则在多进程模式下无法正常工作。
    • 注意事项
      • 最好指定端口, 否则开发工具崩溃后, 重新运行, 会被重新分配端口, 避免带来不必要的问题
  • 框架保留问题, 需要找时间进行优化
    • 框架文件过多(超过3w+文件…)
      • 优化方向:减少不必要的扩展.
    • 重启速度需要再进行研究优化,在ssd的情况下, 重启服务需要大概4,5秒, 较影响开发, 不过配合调试模式, 可以弥补.
      • 优化方向:考虑单独写个启动脚本, 抛弃一些特性来实现热更新, 具体再观望看看别人有没有其它实现. 尽可能实现修改后马上可见.
    • 页面组件化
      • 优化方向:无
        • 还在学习和了解中.