标签归档:Node

如何 parse 一个 typescript interface

blog 编程相关 Node posts 编程相关 typescript ast

目标

有如下一个接口定义,我们想把它的结构 parse 出来,知道每个字段的定义和注释,方便我们生成文档

import {IBanner} from './base/IBanner'
import {IProductGroup} from './base/IProductGroup'
/**
* 投资列表页面
*/
export interface IProductList {
/**
* 产品分组
*/
groupList: Array<IProductGroup>
/**
* 产品列表
*/
list: Array<{
a: string,
b: number
}>
/**
* 投资列表页面的banner
*/
banners: Array<IBanner>
}

typescript 提供的 ast

import * as ts from 'typescript'
const {options} = ts.convertCompilerOptionsFromJson({}, '.', 'tsconfig.json')
options.skipLibCheck = true
options.skipDefaultLibCheck = true
let program = ts.createProgram([
'./src/dashboard/interface/IProductService'
], options)
for (let sourceFile of program.getSourceFiles()) {
if (sourceFile.fileName.includes('IProductService.ts')) {
console.log('fileName', sourceFile.fileName)
console.log('statements', sourceFile.statements[1].members[])
}
}

节选一段输出:

{
pos: 38,
end: 276,
flags: 0,
transformFlags: undefined,
parent: undefined,
kind: 235,
jsDoc:
[ NodeObject {
.....
tags: undefined,
comment: '投资列表页面' } ],
decorators: undefined,
modifiers:
[ TokenObject { pos: 38, end: 64, flags: 0, parent: undefined, kind: 84 },
pos: 38,
end: 64 ],
name:
IdentifierObject {
.....
escapedText: 'IProductList' },
typeParameters: undefined,
heritageClauses: undefined,
members:
[ NodeObject {
.....
jsDoc: [Array],
modifiers: undefined,
name: [Object],
questionToken: undefined,
type: [Object] },
....

原始的 ast ,需要自己写程序遍历完整结构。

ts-simple-ast

ts-simple-ast 提供了一些元素获取的方法,例如
getInterface
getProperties

可以快速拿到你要的对象。
不足是,无法识别 import 进来的内容,例如

interfaceFile.getInterface('IBanner')

是无法获取的,因为 IBanner 是引用其他文件的

import {Project} from 'ts-simple-ast'
// initialize
const project = new Project()
project.addSourceFilesFromTsConfig('./tsconfig.json')
const interfaceFile = project.getSourceFile('src/dashboard/interface/IProductService.ts')
console.log('getProperties', interfaceFile.getInterface('IProductList').getProperties())

readts

readts 这个开源包虽然很冷门,但它却更符合我们的需求,它可以 parse 出项目里所有的 class 和 interface 等,而且连 import 进来的对象也帮你 ref 好了。

遗憾的是,匿名的对象定义还是无法 parse。

{
a: string,
b: number
}

用法:

const readts = require('readts');
const lodash = require('lodash');
var parser = new readts.Parser();
// Read configuration used in the project we want to analyze.
var config = parser.parseConfig('tsconfig.json');
// Modify configuration as needed, for example to avoid writing compiler output to disk.
config.options.noEmit = true;
// Parse the project.
var tree = parser.parse(config);
var interfaceList = lodash(tree)
.filter(item => item.interfaceList.length > 0)
.concat()
.map('interfaceList')
.map(item => item[0])
// .filter(item => item.name && item.name.match(/^I.*/))
// .value()
.find(item => item.name === 'IProductList');
console.log('interfaceList', interfaceList);

输出示例:

ClassSpec {
name: 'IProductList',
pos:
{ sourcePath: '/Users/nick/nodePro/klg-app/src/dashboard/interface/IProductService.ts',
firstLine: 6,
lastLine: 22 },
symbol:
SymbolObject {
flags: 64,
escapedName: 'IProductList',
declarations: [ [Object] ],
members:
Map {
'groupList' => [Object],
'list' => [Object],
'banners' => [Object] },
parent:
SymbolObject {
flags: 512,
escapedName: '"/Users/nick/nodePro/klg-app/src/dashboard/interface/IProductService"',
declarations: [Array],
exports: [Object],
valueDeclaration: [Object],
id: 8410 },
documentationComment: [ [Object] ],
id: 8359 },
doc: '投资列表页面',
propertyList:
[ IdentifierSpec {
name: 'groupList',
type: [Object],
optional: false,
pos: [Object],
doc: '产品分组' },
IdentifierSpec {
name: 'list',
type: [Object],
optional: false,
pos: [Object],
doc: '产品列表' },
IdentifierSpec {
name: 'banners',
type: [Object],
optional: false,
pos: [Object],
doc: '投资列表页面的banner' } ] }

AI 考拉 2018 开源汇总(Node 基础架构)

blog 编程相关 Node posts 编程相关

前言

2018 年,考拉开始对现有项目的常用的工具库进行整理,包含日期处理,数字处理,logger 等常用工具,并打包成 npm module,方便各个项目使用。

代码风格

在开发工具库之前,我们统一了编码标准

JavaScript 代码规范

前端 browser 通用
统一使用 eslint-config-klg ,基于 eslint-config-standard 封装
安装说明见文档

Typescript 代码规范

统一使用 tslint-config-klg ,基于 tslint-config-standard 封装

安装说明见文档

脚手架

我们也提供了脚手架 klg-init,来方便大家启动一个新项目。
安装好这个工具后,一键生成项目模板

$ klg-init dest
[klg-init] fetching npm info of klg-init-config
? Please select a boilerplate type (Use arrow keys)
──────────────
❯ module - npm 库项目模板
model - mongoose model 模板 todo
project - JavaScript 后端项目模板 todo
project-ts - Typescript 后端项目模板 todo
admin - 管理后台项目模板 todo

目前支持的模板有:

工具库列表

目前已经发布的工具库有:

  • klg-logger : logger 工具,基于 tracer
  • klg-number : 数字处理,主要解决 node 小数精度问题
  • klg-request-log :http 请求 log 记录,方便排查问题
  • klg-mq : rabbitmq 连接工具
  • klg-mq-koa : 将 mq 和 koa router 无缝结合
  • klg-redlock : 基于 redis 的分布式锁
  • klg-date : 日期处理,基于 moment
  • klg-request : 后端使用的 http 请求工具,基于 superagent
  • klg-retry : 重试工具
  • klg-tracer : 链路追踪工具,该项目实际不能使用,原因见项目内文档

上述项目都可以在我们公司的开源账号找到 https://github.com/kaolalicai?utf8=%E2%9C%93&q=&type=&language=typescript

API 接口与 Typescript Interface

blog 编程相关 Node posts 编程相关

前言

Interface 就是接口,在 typescript 用于类型限定。
在 Node 项目中,我们常用 apidoc 来定义接口文档。
那么,他们俩是否能结合起来呢?

apidoc 的痛点

apidoc Inline Documentation for RESTful web APIs,使用起来非常方便,直接在 api 上添加注释,就可以生成可视化的文档。

但是,在实际业务中,我们有些接口比较复杂,定义返回值是一件比较痛苦的事情,如图:

而且,过了一段时间之后,出于惰性,开发人员会放弃更新 API 文档,导致文档落后于实际。

API Interface

API 未定义的问题

在 js 项目中,api 返回结构一般是无法清晰地看出来的,你得跑一下接口,才知道会返回什么,或者仔细看一遍代码。这种情况下,开发人员维护 API 会面临以下问题:

  • 文档缺失:接口文档缺少对某个字段的定义,不知道该字段作何用处
  • 字段混乱:明明是一个字段,可能在 接口 A 是叫 amount, 在接口 B 却叫 number
  • 重复定义:多个接口中都用到一个叫 Banner 的东西,需要在每个接口文档中定义一遍,稍有改动,需要更改多处

使用 interface 的好处

所以可以尝试使用 typescript 的 interface 来定义 API 的返回值结构,一个接口对应一个 interface。

假设一个 api 是 product info ,定义结构如下:

/**
* 产品明细
*/
export interface IProductInfo {
/**
* 基础利息
*/
baseRate: number
/**
* 加息
*/
bonusRate: number
/**
* 加息提示
*/
bonusRateStr: string
}

另一个 api 是 product list ,可以很方便地进行复用:

/**
* 产品列表
*/
export interface IProductList {
list: Array<IProductInfo>
}

这些接口定义也可以共享给前端(前端也用 typescript 的话)

apidoc + interface

很多时候,你的痛点其实也是其他人的痛点,所以就有了开源项目,通过搜寻,我们可以找到一个 apidoc 的插件 apidoc-plugin-ts,这个插件可以根据 interface 的定义帮你生成 apidoc 需要的 @apiSuccess 内容。
定义一个接口:

filename: ./employers.ts
export interface Employer {
/**
* Employer job title
*/
jobTitle: string;
/**
* Employer personal details
*/
personalDetails: {
name: string;
age: number;
}
}

引入 interface:

@apiInterface (./employers.ts) {Person}

翻译的效果:

@apiSuccess {String} jobTitle Job title
@apiSuccess {Object} personalDetails Empoyer personal details
@apiSuccess {String} personalDetails.name
@apiSuccess {Number} personalDetails.age

还支持数组结构定义呢~
有了这个插件,就可以通过维护 interface 来维护 apidoc 了。

注意:

该插件不支持匿名接口定义数组结构, 如下:

export interface IProductList {
list: Array<{
a: string,
b: string
}>
}

AST 解析代码的时候会拿不到某些属性,会报错

Node.js APM 软件调研报告

blog 编程相关 Node posts 编程相关

前言

最近做 Node 服务的性能优化工作,在 2016年3月,我写过 Node 性能优化 这篇文章, 讲解了常见的 Node 服务缓慢原因,还介绍了 内存泄露 和 cpu profile 的知识。

这一次,是侧重中应用运行信息收集这一块,根据同事 Leo 的介绍,我对一些支持 Node.js 的 APM 软件做了一次调研。

在分别介绍每个软件之前,我要先阐述我使用这些软件的目的:它可以发现慢的 http 请求,并且可以查看当时的 Node 服务状态(包括内存 CPU 占用状态),database 状态(这里是 mongodb),帮助 dev 准确分析出请求处理缓慢的原因。

下文的体验报告将以此目标为标准进行评价。

软件列表

本次体验的 APM 软件有:

名称 介绍 开源/收费情况
atatus 支持多种语言,包括前端 不开源,收费
newrelic 支持多种语言,包括前端 开源,收费
keymetrics pm2, Node 应用管理器 开源,限额免费
Pandora.js 阿里出品,Node 应用管理器 开源,免费(自建服务)
alinode Node 底层的监控 开源,免费(阿里云上配置实例)
statsd + graphite + grafana 监控三件套,开发自由度高 开源,自建服务

atatus

atatus 提供了 npm 工具包 atatus-node 用于监控数据收集,具体配置见官方文档

使用报告:不是 100 % 的采样率,没有收集到官网宣传的各种维度的信息,使用 Koa 框架,基本上只能看到总的 http response time。

newrelic

网址 https://newrelic.com/ ,和 atatus 基本相同,也提供 npm 工具包 newrelic。

keymetrics

偏向应用生命周期管理,可以看到 Node 服务的 cpu 内存等占用情况,有错误(crash)收集功能,国内访问很卡。

Pandora.js

Pandora.js 是一个 Node.js 应用监控管理器。它集成了多种类型的能力诸如:监控、链路追踪、调试、进程管理等等。

它是个很有趣的东西,总结来说是它很强大但是还不够成熟,包括 Pandora 自身和整个 Node 生态都不成熟。

应用管理

可以对标 PM2,不赘述。

应用度量

有操作系统指标,包含 Load、CPU、内存、磁盘、网络、TCP 等各种指标;
有 Node.js 指标(内存占用);
还可以自定义指标,类似 statsd 做的事情,但是后续的存储和展示嘛,非常简单,没有 statsd + graphite + grafana 好用。

链路追踪

这个是 Pandora 提供的比较“先进”的功能,链路追踪理论上可以分析出一个 http 哪里耗时多,例如一个 get 请求,做的事情有,查询 db ,调用 第三方服务等。链路追踪会记录每一个环节的耗时:

链路追踪的实现依赖了 Node 的一个实验性特性 async_hooks,因为是新特性,目前还有很多问题没有解决,例如 mongoose 和 superagent 两个工具包的 Promise 实现方式会导致 async_hooks,具体可以见我在 Pandora 提的 issue ,也就是说,如果你刚好用了这两个框架,Pandora 链路追踪就失效了。

alinode

Node.js 性能平台 https://cn.aliyun.com/product/nodejs ,alinode 是在 node runtime 层面做的应用信息收集,而不是上述各个框架在应用层进行信息收集,所以 alinode 可以监控到 进程数据,堆快照、堆时间线、CPU Profile、GC Trace 等非常底层的信息,如果你的应用性能瓶颈在 Node 服务本身,使用该工具会有很大的帮助,如果你的应用性能瓶颈在 DB,那应该是用 DB 监控工具。

statsd + graphite + grafana

这个三件套有意思的是,监控指标是开发自己定义的,考拉用这三件套监控请求处理时间,记录每个请求的处理时间
这样在 grafana 上可以快速看出当前应用的请求量与趋势,已经快速分析哪些接口缓慢。

接口访问量的趋势变化:

处理最慢的接口:

这套工具的优势在于 grafana, 提供了非常直观的图表。
这套工具的搭建方法我之前也写过文章,可以看看 快速搭建一个监控服务

总结

回到我们的目标“帮助 dev 准确分析出请求处理缓慢的原因”,
理论上最贴合这个目标的是 Pandora.js ,但是我们还需要再等等,等它成熟。
最简单而强大的是 statsd + graphite + grafana 三件套,不用考虑 atatus newrelic 等。
如果你使用 Node 做高并发服务,alinode 将会很有用。

class extends function

我写的东西 编程相关 Node posts

注意:本文示例代码是 typescript

klg-logger 的封装

最近在做一个共用的 logger 包 klg-logger ,在调研了一圈后,决定基于 tracer 这个包来做封装,tracer 足够简单,也具有拓展性。因为公司使用的工具包都是使用 class 风格的, 所以第一版的 klg-logger 长这样:

import {console, Tracer} from 'tracer'
export class Logger {
private logger: any
constructor (config?: Tracer.LoggerConfig) {
this.logger = console(config)
}
log (msg: any, ...params): void {
this.logger.log.apply(this, arguments)
}
error (msg: any, ...params): void {
this.logger.error.apply(this, arguments)
}
// ...
}

使用方式:

const logger = new Logger({})
logger.log('hello world')

就是使用一个 Logger class 把 tracer 封装了一次。

this 域问题

过了一段时间,klg-logger 出现了一个错误:

TypeError: Cannot read property 'logger' of undefined

经排查,是 this 作用域的问题,调用 logger 的代码长这样:

this.connect().then(function () {
// code
}).catch(logger.error)

logger.error 这个 function 是作为 参数直接传递个 catch, 那么 logger.error 内部的 this 将会指向 catch 对象,而不再是 logger 对象。
我们可以改变写法来避免这个错误:

this.connect().then(function () {
// empty
}).catch(function (err) {
logger.error(err.message)
})

但如果要更彻底地解决这个问题,还是要改变一下 logger.error 的实现才行。

class extends function

考虑到这个包 klg-logger 已经在多个系统中使用,为了保持兼容,我们要继续保持 class 风格,所以我们要把上文提到的“封装”的实现改为继承实现。

import {console} from 'tracer'
export class Logger extends console {
// Error:(3, 29) TS2507: Type '(config?: LoggerConfig) => Logger' is not a constructor function type.
}

直接继承 tracer 的 console 是会报错的,因为 console function 没有 construction。
在 stackoverflow 上可以找到解决方案:https://stackoverflow.com/questions/36871299/how-to-extend-function-with-es6-classes

通过一个 Function 把 console 包装成一个具有 construction 的对象,然后在 construction 里面 return console 实例即可

class Logger extends Function implements Tracer.Logger {
constructor (config?: LoggerConfig) {
super()
Object.setPrototypeOf(console, Logger.prototype)
const instance = console(config as any) as Logger
instance.err = instance.error
return instance
}
// 下列方法都会被覆盖,只做声明用, 无需实现
debug (...args: any[]): Tracer.LogOutput {
return undefined
}
error (...args: any[]): Tracer.LogOutput {
return undefined
}
/// ......
}

经测试,最后的写法和功能都是原版保持了一致。

知识库

blog 编程相关 Node 编程相关 posts

方法论

编程的精进之法 任务列表法+PDCA
像机器一样思考 input -> process -> output
然而培训并没有什么用 最后一块吃饱的饼+练习
软件开发工作的第一现场 软件开发工作的第一现场,在语言里。使用 uml 这类的工具统一语言可以极大地提升沟通效率
聊聊clean code
clean-code-javascript
由屎色自行车棚引发的思考 避免讨论无关紧要但会引起大量争论的问题
X-Y PROBLEM 问问题的时候没有去问怎么解决问题X,而是去问解决方案Y应该怎么去实现和操作。

编程知识与概念

浅谈命令查询职责分离(CQRS)模式
Unit of Work
Thinking In Design Pattern——Unit Of Work(工作单元)模式探索 – 木宛城主 – 博客园

微服务与RPC
谈谈后端业务系统的微服务化改造
如何选择开源许可证?

深度学习

深度 | 机器学习敲门砖:任何人都能看懂的TensorFlow介绍
面向普通开发者的机器学习入门
深度学习系列视频 bilibili
深度学习系列视频 中文 YouTube
深度学习系列视频 YouTube

Web 框架

Spring Boot
Koa
Express
Egg.js

数据库

用Redis构建分布式锁

算法与数据结构

基于用户投票的排名算法
跳跃表Skip List的原理和实现 多层链表

操作系统

怎样理解阻塞非阻塞与同步异步的区别

网络 Http

一个Option请求引发的深度解析
总结 XSS 与 CSRF 两种跨站攻击 XSS: 注入脚本,收集用户信息, CSRF:冒充用户之手
一文读懂 HTTP/2 特性
什么是JS跨域访问?

Node 常用库

Building a typescript library
Pandora.js 应用管理器
PM2 应用管理器
lodash 集合库
moment 日期库
mocha 测试库
autod Auto generate dependencies and devDependencies by parse the project file.
玩转npm

运维

shadowsocks server 搭建

js 基础知识

Node.js 8 Node 8 新特性介绍
JavaScript中的稀疏数组与密集数组
js 异常处理最佳实践 详细列出了 js 异常处理的发展历史,也补充了非常多的基础知识
Babel是如何读懂JS代码的 三个阶段:

  1. 解析:将代码字符串解析成抽象语法树
  2. 变换:对抽象语法树进行变换操作
  3. 再建:根据变换后的抽象语法树再生成代码字符串

前端高手必备:详解 JavaScript 柯里化

做个 SSO 系统

我写的东西 编程相关 Node posts 编程相关

应用场景

最近公司要统一内部管理系统的用户与权限控制,于是考虑开发一个 SSO 单点登陆系统,考虑到内部系统都是 web 服务和开发时间有限,而且内部系统都是 Node.js 写的, 使用 Koa 框架,最终决定使用最简单的解决方案。

实现方案

原理

kwt + cookie
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,这里将作为用户的登陆标识。
用户的登陆流程是这样的:

  1. 打开接入方系统,假设域名是 aaa.abc.com
  2. 校验登陆状态,查看 cookie 里是否存在 jwt 并解密校验,如果未登录,跳转到 SSO 登陆页面,SSO 域名是 sso.abc.com
  3. 在 SSO 登陆页面填写用户名和密码(未注册的先注册),登陆后 SSO 系统把 jwt 写入 cookie,cookie 的有效域名为 abc.com,然后跳跳转回原系统(跳过来的时候在 url 里带上原系统地址)
  4. 返回原系统,校验登陆状态,登陆完成

sso 系统实现

基本的用户管理功能,有:

  • 部门管理
  • 权限管理
  • 注册功能
  • 登陆功能
  • 找回密码

重点功能是登陆,需要在登陆时将 jwt 写入 cookie,上文原理中有要求各个系统要有个共同的主域名 abc.com,各个系统不同域的话,那就只能把 jwt 在 url 中返回给原系统了,原系统自己把 jwt 写入 cookie,来看看这部分的代码:

const jwtdata = {
username: user.username,
email: user.email,
role: user.role
}
// 生成 jwt
const token = jwt.sign(jwtdata, Const.TOKEN_SECRET, {expiresIn: '365d'})
// 写入 cookie
ctx.cookies.set(
'token',
token,
{
domain: '.abc.com', // 写cookie所在的域名
path: '/', // 写cookie所在的路径
maxAge: 30 * 60 * 1000, // cookie有效时长
httpOnly: false, // 是否只用于http请求中获取
overwrite: true // 是否允许重写
}
)

注意用于加密 jwt 的密匙,内部系统的话可以直接用一个密匙加密,然后接入方都用这个密匙解密就可以了。
如果安全要求高一点,那就是 sso 系统用公钥(保密)加密 jwt,然后接入方使用私钥(公开)解密。

接入方实现

考虑到接入方有多个,所以考虑直接写个 Koa 中间件给各个接入方调用是最简单的,将此中间件作为 npm 包发布,接入方使用该中间件监听所有请求,完成对 jwt 的解析和校验。

监控请求

const Koa = require('koa')
const app = new Koa()
const auth = require('sso-auth')
app.use(auth.validate({
unless: [//register/, //login/, //message/],
errHandle: async function (ctx) {
console.log('授权错误')
// 跳转到SSO的登陆页面或者 返回url给前端跳转
ctx.body = {
code: 3,
message: '授权错误',
url: 'https://sso.abc.com'
}
ctx.status = 200
}
}))

中间件 sso-auth 的内部实现

// 从 cookie 取出 jwt
// 校验 jwt 是否符合

假设接入方是其他编程语言的系统,那就对该语言实现对应的上述校验逻辑即可。

总结

jwt 作为 token 可以附带额外的信息
使用 cookie 存储 token ,同域名的系统接入非常方便
使用中间件方便了接入方校验 token

Node.js 并发模型

我写的东西 编程相关 Node posts 编程相关

前言

首先,什么是并发?

并发是指程序可以同时处理多个任务,是一个web 服务必备的能力。

自从 Nodejs 出现后,js 开始涉及后端领域,因为其出色的并发模型,被很多企业用来处理高并发请求,例如淘宝已经大量使用 node 处理中间层业务。

接下来本文就分析一下 js 的并发模型,来理解 node 相对于其他语言的优势以及其最合适的应用场景

tips:并发和并行区别

异步IO

什么是异步IO?
异步 IO 具体是如何实现的呢?
异步和同步有什么区别呢?
异步就不阻塞吗?IO 阻塞又是什么概念呢?
带着这些问题,我们慢慢分析。

IO 模型

《UNIX网络编程:卷一》第六章——I/O复用。书中向我们提及了5种类UNIX下可用的I/O模型:

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用(select,poll,epoll…);
  • 信号驱动式I/O(SIGIO);
  • 异步I/O(POSIX的aio_系列函数);

总结一下阻塞,非阻塞,同步,异步

  • 阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;
  • 同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。

说人话

上面的解释太复杂我看不懂怎么办?我们把上文说到 IO 代入到生活的场景中,考虑到我们公司很多人喜欢买一点点饮料,就以买饮料为例,将四种常见 IO 模型转换为对应的买饮料流程。下面是一下设定:

  • 把买一杯一点点的流程简化为两步:下单制作和拿一点点回公司
  • 公司员工 === 线程
  • 下单制作 === 发起IO请求
  • 拿一点点回公司 === 读取数据

异步 IO 的特点就是把 IO 处理的事情都交给了操作系统(美团外卖),这样线程就不会被 IO 阻塞,可以继续处理其他请求

Node 的异步IO

Node.js 的异步 IO 由 Libuv 这个库提供实现,Libuv 是 Node.js 关键的一个组成部分,它为上层的 Node.js 提供了统一的 API 调用,使其不用考虑平台差距,隐藏了底层实现。

可以看出,它提供了非阻塞的网络 I/O,异步文件系统访问等功能,而且右下角居然还有个线程池,实际上 Libuv 收到的 IO 请求是同个多线程来实现的, 看来 Node 只是在程序层面单线程而已

事件循环

任务队列

先看看 Node.js 结构

根据上图,Node.js的运行机制如下。

(1)V8引擎解析JavaScript脚本。

(2)解析后的代码,调用Node API。

(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎(callback 处理结果)。

(4)V8引擎再将结果返回给用户。

异步操作都会被压入任务队列,当任务队列为空的时候,程序退出。

示例代码

总结

Libuv 使用异步IO + 线程池实现的事件循环处理机制提供的高效的 IO 处理,是 Node 能承担高并发请求的主要原因

参考文章与书籍

《深入浅出Node.js》
《Unix 网络编程》
《七周七并发模型》
并发与并行的区别
JavaScript 运行机制详解:再谈Event Loop
怎样理解阻塞非阻塞与同步异步的区别? – 大姚的回答 – 知乎
Node.js 探秘:初识单线程的 Node.js
Linux IO模式及 select、poll、epoll详解

UML

我有哪些提升编程工作效率的方法

1. 列出每日TODO

最简单也是最有效的方法,每天早上整理好今天待办事项,按照紧急和重要两个指标划分四象限,优先处理重要的事情,紧急但不重要的可次之。
这个做法的好处是做事会有计划有条理。

2. 做好工作笔记

使用云笔记记录每个 task 的相关信息,整理素材。云笔记的好处是不怕丢,手机也能方便查询,也方便日后回顾,特别是年终的时候 看着一个个 task 记录,就知道今年做了哪些事情。

3. 坚决地自动化

如果是重复简单的工作,就尽量使用程序脚本解决,可能第一次做自动化,写脚步的时间比直接做还慢,但是基础的脚本都差不多,需要用到自动化的一般就是那几类,有了积累之后,下次使用就方便很多了。珍惜自己的时间,重视自己的价值,工程师不应该把时间浪费在简单重复的事情上。

4. 熟练使用工具,节省时间和精力

人一天的精力有限,而且人能连续集中精力时间也不长,番茄工作法认为人平均能集中精力的时长为 25 分钟,并以此为周期,每个周期之间需要休息 5 分钟。所以在编码时,需要熟悉 IDE 的各种快捷功能,提高工作。

举个例子:使用 IDE 提供的 debug 功能,可以在断点处之前查看上下文变量,还可以修改上下文,也可以给短点设置变量,debug 效率非常高。

使用 console.log 来 debug 同样可以找出问题,但需要浪费时间在输入 console.log 和重试上面。即使只有几秒钟,毕竟一个周期也只有 25 分钟,而且我自己感觉一天能高效工作的时间也就 3 小时左右 LOL。

5. 先思考,再编码,多画图

不要把实现方案想个大概就动工,不然写到一半才发现某些地方没考虑完整,又得推翻部分设计,返工浪费的时间其更多。
处理简单的问题,可以脑子里过一遍流程,把可能影响的地方都一一列出来,再整理好 TODO ,编码的时候其实就只需要关注把 TODO 翻译成代码即可。
如果问题比较复杂,那就把画出流程图或者时序图,可以帮你有效地整理流程。画图还有另一个好处:当做问题,日后接收的人也轻松。这里推荐使用 plantUML 来画各种流程图,类似 markdown 只需要写好文本,渲染后就是标准的 UML 图了。

IDE 常用功能 for Node.js

我写的东西 编程相关 Node posts

什么是 IDE

集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序。使用 nodejs 开发后端应用的时候,我的常用 IDE 是 Webstorm,偶尔也用 vs code。
根据我的日常使用经验,本文列举下 Webstorm 这个 IDE 提供了哪些比较实用功能,这些功能基本上 vs code 也支持。

编辑

文件跳转

跳转都任意文件
搜索功能

快捷键

导出快捷键列表,仔细过一遍,

自动补全

自动补全功能的实现一般有两种

  1. 正则匹配,自动匹配出现过的字符
  2. 语法树分析,因为 js 是动态类型语言,IDE 根据语法树预知变量类型,但是 typescript 是可以的,vs code 也会根据 js 库里的 d.ts 描述文件来做自动补全,所以 typescript 项目的自动补全体验会更好。

Refactor 重构

常见重构支持:
重命名
Inline : 表达式替换变量
Extract :变量替换表达式

该功能在 typescript 项目中会更强大

模板功能

把常见的代码块设置为模板,方便快速输入。
例如把 console.log('$message$); 这类常用语句定义为模板后定义关键字为 log ,只要输入 log 补全即可。

TODO 列表

列出项目里的所有 TODO,时刻提醒你还债~

eslint / tslint

集成 eslint / tslint 等 js 代码检查工具,错误代码直接标红。

Format

自动的格式化功能呢,支持 standard

运行

Build

支持 typescript flow 等语言的编译

Run

run 配置,支持以下常见库的运行配置。

在 Webstorm 中,配置好一个 run 配置之后,可以快速 debug 。

Debug

提供 debug 支持,断点比 console.log 清晰,也不会留下一堆乱七八糟的 log 代码,而且断点的设置还支持条件过滤,非常方便

Unit Test

清晰的 test 结果

Coverage

代码覆盖率直接显示到编辑器上,如下图,line 37-39 左侧是淡绿色,表示测试覆盖到了,下方的淡红色表明测试未覆盖

Deploy

支持 ftp 上传,IDE 也是可以直接在服务器编程的
就是自动帮你执行 ftp 上传命令

Terminal

当前目录开启 terminal

版本控制

支持主流的版本控制软件

git svn csv

Commit

除了 GUI commit 视图

还有 before commit 和 after commit 钩子

Review

review 视图帮助你清晰了解变更,强烈建议每次 commit 之前都要 review 一次。

Revert

撤销更改,git checkout – [filename]

冲突解决

解决代码冲突,gui 视图,会清晰很多

Shelve 暂存

临时切换任务使用,就不用提交一个 “暂存” commit 了

ChangeList/Task

任务汇总,整理变更

History

  1. version control 的 history 查看历史变更
  2. Annotate,查锅神器,同 git blame ,可以看到每一行代码的最后编辑人
  3. local history 防丢失非常重要

集成外部软件

有名的外部软件基本都能找到集成,我常用的有

Jira

直接获取 task ,根据 task 信息创建分支,自动填充 commit 信息

Plantuml

有插件,将 uml 渲染成图片

总结

上文列举了后端开发中比较实用的功能,但每个人的工作习惯都不一样,如何知道有哪些功能是你很喜欢的但是你还没发现呢?

仔细过一遍 IDE 的菜单就好了