简

人生短暂,学海无边,而大道至简。


  • 首页

  • 归档

  • 分类

  • 标签

ContiPerf测试工具

发表于 2019-04-23 | 分类于 其他

ContiPerf是一个轻量级的测试工具,基于JUnit 4 开发,可用于接口级的性能测试,可以指定在线程数量和执行次数,通过限制最大时间和平均执行时间来进行效率测试。

常用的参数如下:@PerfTest(invocations = 100,threads = 10)

invocations() : 执行次数与线程无关
duration(): 间隔时间
threads():线程数

添加依赖包

1
2
3
4
5
6
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
private @Autowired UserService userService;
//引入 ContiPerf 进行性能测试
@Rule
public ContiPerfRule rule = new ContiPerfRule();


//10个线程 执行100次
@Test
@PerfTest(invocations = 100,threads = 10)
public void test(){
Long id = (long) (Math.random()*100);
userService.selectId(id);
}

在junit执行完毕,会在target/contiperf-report中有相关的执行结果,可以使用浏览器打开查看

Nodejs-uuid

发表于 2019-04-21 | 分类于 Node

UUID 定义

UUID 是由一组 32 位的 16 进制数所构成,是故 UUID 理论上的总数为 1632=2128,约等于 3.4 x 10^38。也就是说若每纳秒产生 1 兆个 UUID,要花 100 亿年才会将所有 UUID 用完。

UUID 示例:74760410-f963-11e8-b2a3-1bb26e1e5b69

UUID 版本

1
2
3
4
"版本 1" UUID 是根据时间和节点 ID(通常是 MAC 地址)生成;
"版本 2" UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;
"版本 3" 和 "版本 5" 确定性 UUID 通过散列 (hashing) 命名空间 (namespace) 标识符和名称生成;
"版本 4" UUID 使用随机性或伪随机性生成。

Node.js uuid 模块示例 在 JavaScript 中生成符合 RFC 规范的 UUID。

版本 1:基于时间的 UUID

1
2
3
4
const uuidv1 = require('uuid/v1');
uuidv1() // ⇨ 43d7e120-f963-11e8-999e-51f3e5aa256f

const formatedUUID = uuidv1().replace(/-/g, ''); // 去除横线-

版本 2:DCE 安全的 UUID

1
未实现。

版本 3:基于名字空间的 UUID(MD5)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const uuidv3 = require('uuid/v3');

// ... using predefined DNS namespace (for domain names)
uuidv3('hello.example.com', uuidv3.DNS); // ⇨ '9125a8dc-52ee-365b-a5aa-81b0b3681cf6'

// ... using predefined URL namespace (for, well, URLs)
uuidv3('http://example.com/hello', uuidv3.URL); // ⇨ 'c6235813-3ba4-3801-ae84-e0a6ebb7d138'

// ... using a custom namespace
//
// Note: Custom namespaces should be a UUID string specific to your application!
// E.g. the one here was generated using this modules `uuid` CLI.
const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
uuidv3('Hello, World!', MY_NAMESPACE); // ⇨ 'e8b5a51d-11c8-3310-a6ab-367563f20686'

版本 4:基于随机数的 UUID

1
2
const uuidv4 = require('uuid/v4');
uuidv4(); // ⇨ 57751a52-db27-4b2f-8a08-7e02f9e94a42

版本 5:基于名字空间的 UUID(SHA1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const uuidv5 = require('uuid/v5');

// ... using predefined DNS namespace (for domain names)
uuidv5('hello.example.com', uuidv5.DNS); // ⇨ 'fdda765f-fc57-5604-a269-52a7df8164ec'

// ... using predefined URL namespace (for, well, URLs)
uuidv5('http://example.com/hello', uuidv5.URL); // ⇨ '3bbcee75-cecc-5b56-8031-b6641c1ed1f1'

// ... using a custom namespace
//
// Note: Custom namespaces should be a UUID string specific to your application!
// E.g. the one here was generated using this modules `uuid` CLI.
const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
uuidv5('Hello, World!', MY_NAMESPACE); // ⇨ '630eb68f-e0fa-5ecc-887a-7c7a62614681'

mybatis源码日志分析

发表于 2019-04-20 | 分类于 mybatis

mybatis日志分析

初始化:org.apache.ibatis.logging.LogFactory

mybatis内置日志工厂提供日志功能,提供很多日志的实现类,用来记录日志,取决于初始化的时候按顺序load到的class。默认是用Slf4j(第一个)

1
2
3
4
5
6
7
8
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}

没有则继续找:

1
2
3
4
5
6
7
8
9
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable var2) {
}
}

}

Mybatis 中 SQL 语句的日志级别被设为DEBUG(JDK 日志设为 FINE)

默认情况下,mybatis+spring5不会打印输出mybatis日志,这是因为集成后spring5默认的日志是spring-jcl 最终选择的是jul,而jul默认日志级别为info,所以也不会打印Mybatis执行的日志。并且sql执行日志是debug级别。

可以加入log4j2的包,不用做任何更改就可以打印日志。

如果加入log4j包,并且配置mybatis选择使用该日志就可以。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//配置log
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(Log4jImpl.class);
factoryBean.setConfiguration(configuration);
factoryBean.setDataSource(dataSource);
return factoryBean;
}
或者,在实例初始化前执行,推荐前面一种
@PostConstruct
public void init(){
//org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
//org.apache.ibatis.logging.LogFactory.useJdkLogging();
//org.apache.ibatis.logging.LogFactory.useCommonsLogging();
//org.apache.ibatis.logging.LogFactory.useStdOutLogging();
}

如果加入log4j2就会打印,spring5使用了log4j默认是用log4j2。注意修改日志配置的级别。

这都是使用java配置方式,xml不做讨论。

此外,也可以扩展通过默认的方式打印日志信息。

自定义扩展类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MyLog implements org.apache.ibatis.logging.Log {
private Logger logger;

public MyLog(String clazz) {
this.logger = Logger.getLogger(clazz);
}

@Override
public boolean isDebugEnabled() {
return true;
}

@Override
public boolean isTraceEnabled() {
return false;
}

@Override
public void error(String s, Throwable throwable) {

}

@Override
public void error(String s) {

}

@Override
public void debug(String s) {
logger.info(s);
}

@Override
public void trace(String s) {

}

@Override
public void warn(String s) {

}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {

SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//配置log
LogFactory.useCustomLogging(MyLog.class);
//或
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(MyLog.class);
factoryBean.setConfiguration(configuration);
factoryBean.setDataSource(dataSource);
return factoryBean;
}

ES6—babel

发表于 2019-04-20 | 分类于 Node

Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

一、配置文件.babelrc 和转码规则

Babel的配置文件是.babelrc,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。

该文件用来设置转码规则和插件,基本格式如下。

1
2
3
4
{
"presets": [],
"plugins": []
}

presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

1
2
3
4
5
6
7
8
9
10
11
#ES2015转码规则
$ npm install --save-dev babel-preset-es2015

#react转码规则
$ npm install --save-dev babel-preset-react

#ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
$ npm install --save-dev babel-preset-stage-0
$ npm install --save-dev babel-preset-stage-1
$ npm install --save-dev babel-preset-stage-2
$ npm install --save-dev babel-preset-stage-3

然后,将这些规则加入.babelrc。

1
2
3
4
5
6
7
8
{
"presets": [
"es2015",
"react",
"stage-2"
],
"plugins": []
}

注意,以下所有Babel工具和模块的使用,都必须先写好.babelrc。

二、命令行转码babel-cli

Babel提供babel-cli工具,用于命令行转码。npm install –save-dev babel-cli

命令行中调用babel,两种方式:

1、配置文件中加入:

“build”: “babel src -d lib”

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "6-babel",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "babel src -d lib"
},
"author": "",
"license": "ISC",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-es2015": "^6.24.1"
}
}

npm run build

2、进入node_modules文件夹,再进入.bin文件夹,然后执行在命令行中执行babel src -d lib

3、测试

新建一个文件,写入ES6语法,直接运行,报错

执行npm run build

结果:

再运行,没问题

三、babel-node

babel-cli工具自带一个babel-node命令,提供一个支持ES6的REPL环境。它支持Node的REPL环境的所有功能,而且可以直接运行ES6代码。

它不用单独安装,而是随babel-cli一起安装。然后,执行babel-node就进入PEPL环境。

1
2
3
$ babel-node
> (x => x * 2)(1)
2

babel-node命令可以直接运行ES6脚本。将上面的代码放入脚本文件es6.js,然后直接运行。

1
2
$ babel-node es6.js
2

babel-node也可以安装在项目中。$ npm install –save-dev babel-cli

然后,改写package.json。

1
2
3
4
5
{
"scripts": {
"script-name": "babel-node script.js"
}
}

上面代码中,使用babel-node替代node,这样script.js本身就不用做任何转码处理。

四、babel-register

babel-register模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js、.jsx、.es和.es6后缀名的文件,就会先用Babel进行转码。

$ npm install –save-dev babel-register

使用时,必须首先加载babel-register。

require(“babel-register”);
require(“./index.js”);

然后,就不需要手动对index.js转码了。

babel-register只会对require命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。

五、babel-core

如果某些代码需要调用Babel的API进行转码,就要使用babel-core模块。

$ npm install babel-core –save

六、babel-polyfill

Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

$ npm install –save babel-polyfill

然后,在脚本头部,加入如下一行代码。

1
2
3
import 'babel-polyfill';
// 或者
require('babel-polyfill');

Babel默认不转码的API非常多,详细清单可以查看babel-plugin-transform-runtime模块的definitions.js文件。

七、浏览器环境

Babel也可以用于浏览器环境。但是,从Babel 6.0开始,不再直接提供浏览器版本,而是要用构建工具构建出来。如果你没有或不想使用构建工具,可以通过安装5.x版本的babel-core模块获取。

$ npm install babel-core@old

运行上面的命令以后,就可以在当前目录的node_modules/babel-core/子目录里面,找到babel的浏览器版本browser.js(未精简)和browser.min.js(已精简)。

然后,将下面的代码插入网页。

1
2
3
4
<script src="node_modules/babel-core/browser.js"></script>
<script type="text/babel">
// Your ES6 code
</script>

上面代码中,browser.js是Babel提供的转换器脚本,可以在浏览器运行。用户的ES6脚本放在script标签之中,但是要注明type=”text/babel”。

另一种方法是使用babel-standalone模块提供的浏览器版本,将其插入网页。

1
2
3
4
5
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.4.4/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>
注意,网页中实时将ES6代码转为ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。

下面是如何将代码打包成浏览器可以使用的脚本,以Babel配合Browserify为例。首先,安装babelify模块。

1
2
3
4
5
$ npm install --save-dev babelify babel-preset-es2015
然后,再用命令行转换ES6脚本。

$ browserify script.js -o bundle.js \
-t [ babelify --presets [ es2015 react ] ]

上面代码将ES6脚本script.js,转为bundle.js,浏览器直接加载后者就可以了。

在package.json设置下面的代码,就不用每次命令行都输入参数了。

1
2
3
4
5
{
"browserify": {
"transform": [["babelify", { "presets": ["es2015"] }]]
}
}

TypeScript——orm框架-TypeORM

发表于 2019-04-19 | 分类于 Node

在nodejs中也有比较好用的ORM框架,比如TypeORM,Sequelize等等。

TypeORM 是一个 ORM 框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5,ES6,ES7,ES8)一起使用。 它的目标是始终支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的(不管是只有几张表的小型应用还是拥有多数据库的大型企业应用)应用程序。

不同于现有的所有其他 JavaScript ORM 框架,TypeORM 支持 Active Record 和 Data Mapper 模式,这意味着你可以以最高效的方式编写高质量的、松耦合的、可扩展的、可维护的应用程序。

TypeORM 参考了很多其他优秀 ORM 的实现, 比如 Hibernate, Doctrine 和 Entity Framework。

TypeORM 的一些特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
同时支持 DataMapper 和 ActiveRecord (随你选择)
实体和列
数据库特性列类型
实体管理
存储库和自定义存储库
清晰的对象关系模型
关联(关系)
贪婪和延迟关系
单向的,双向的和自引用的关系
支持多重继承模式
级联
索引
事务
迁移和自动迁移
连接池
主从复制
使用多个数据库连接
使用多个数据库类型
跨数据库和跨模式查询
优雅的语法,灵活而强大的 QueryBuilder
左联接和内联接
使用联查查询的适当分页
查询缓存
原始结果流
日志
监听者和订阅者(钩子)
支持闭包表模式
在模型或者分离的配置文件中声明模式
json / xml / yml / env 格式的连接配置
支持 MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js
支持 MongoDB NoSQL 数据库
可在 NodeJS / 浏览器 / Ionic / Cordova / React Native / Expo / Electron 平台上使用
支持 TypeScript 和 JavaScript
生成高性能、灵活、清晰和可维护的代码
遵循所有可能的最佳实践
命令行工具

安装

1
2
npm install typeorm --save
npm install reflect-metadata --save

并且需要在应用程序的全局位置导入(例如在app.ts中)

1
import "reflect-metadata";

你可能还需要安装 node typings(以此来使用 Node 的智能提示):

1
npm install @types/node --save

安装数据库驱动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MySQL 或者 MariaDB
npm install mysql --save (也可以安装 mysql2)

PostgreSQL
npm install pg --save

SQLite
npm install sqlite3 --save

Microsoft SQL Server
npm install mssql --save

sql.js
npm install sql.js --save

Oracle
npm install oracledb --save
  • TypeScript 配置 *
    此外,请确保你使用的 TypeScript 编译器版本是2.3或更高版本,并且已经在 tsconfig.json 中启用了以下设置:
    1
    2
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    除此之外,你可能还需要在编译器选项的 lib 中启用 es6,或者安装 es6-shim 的 @types。

快速开始

全局安装 TypeORM:npm install typeorm -g

然后转到要创建新项目的目录并运行命令:typeorm init –name MyProject –database mysql

其中 name 是项目的名称,database 是将使用的数据库。

数据库可以是以下值之一: mysql、 mariadb、 postgres、 sqlite、 mssql、 oracle、 mongodb、 cordova、 react-native、 expo、 nativescript.

此命令将在 MyProject 目录中生成一个包含以下文件的新项目:

1
2
3
4
5
6
7
8
9
10
11
MyProject
├── src // TypeScript 代码
│ ├── entity // 存储实体(数据库模型)的位置
│ │ └── User.ts // 示例 entity
│ ├── migration // 存储迁移的目录
│ └── index.ts // 程序执行主文件
├── .gitignore // gitignore文件
├── ormconfig.json // ORM和数据库连接配置
├── package.json // node module 依赖
├── README.md // 简单的 readme 文件
└── tsconfig.json // TypeScript 编译选项

或者在现有 node 项目上运行 typeorm init,但要注意,此操作可能会覆盖已有的某些文件。

1
2
D:\mydoc\NodeJs\前端-study-demo\typeorm-demo>typeorm init
Project created inside current directory.

接下来安装项目依赖项:npm install

在安装过程中,编辑 ormconfig.json 文件并在其中编辑自己的数据库连接配置选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//ormconfig.json
设置 synchronize 可确保每次运行应用程序时实体都将与数据库同步
{
"type": "mysql",
"host": "172.16.3.34",
"port": 3306,
"username": "root",
"password": "root",
"database": "root",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
}
//tsconfig.json
{
"compilerOptions": {
"lib": [
"es5",
"es6"
],
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./build",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
}
//package.json
{
"name": "typeorm-demo",
"version": "0.0.1",
"description": "Awesome project developed with TypeORM.",
"devDependencies": {
"ts-node": "3.3.0",
"@types/node": "^8.0.29",
"typescript": "3.3.3333"
},
"dependencies": {
"typeorm": "0.2.22",
"reflect-metadata": "^0.1.10",
"mysql": "^2.14.1"
},
"scripts": {
"start": "ts-node src/index.ts"
}
}

运行:

1
2
3
4
5
6
7
8
9
10
11
12
> typeorm-demo@0.0.1 start D:\mydoc\NodeJs\前端-study-demo\typeorm-demo
> ts-node src/index.ts

Inserting a new user into the database...
Saved a new user with id: 11
Loading users from the database...
Loaded users: [ User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 2, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 3, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 10, firstName: 'sundial', lastName: '3434', age: 20 },
User { id: 11, firstName: 'Timber', lastName: 'Saw', age: 25 } ]
Here you can setup and run express/koa/any other framework.

绝大多数情况下,你只需要配置 host, username, password, database 或者 port 即可。

完成配置并安装所有 node modules 后,即可运行应用程序:npm start

还可以通过运行 typeorm init –name MyProject –database mysql –express 来生成一个更高级的 Express 项目

分步指南

创建实体

1
2
3
4
5
6
7
8
9
10
11
12
13
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}

每个表都必须至少有一个主键列。使用 @PrimaryColumn 修饰符。自动生成的列@PrimaryGeneratedColumn()

列数据类型

见最后

创建数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
Photo//引用目录下的所有实体__dirname + "/entity/*.js"
],
synchronize: true,
logging: false
}).then(connection => {
// 这里可以写实体操作相关的代码
}).catch(error => console.log(error));

可以吧数据库配置信息放到ormconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"name": "default",
"type": "mysql",
"host": "172.16.3.34",
"port": 3306,
"username": "root",
"password": "root",
"database": "sonar",
"synchronize": true,
"logging": false,
"cache": true,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.{ts,js}"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "reflect-metadata";
import {createConnection} from "typeorm";
import {User} from "./entity/User";
createConnection().then(connection => {
// 这里可以写实体操作相关的代码
connection.manager.find(User).then(o=>console.log("Loaded users: ", o));
}).catch(error => console.log(error));

结果:
D:\mydoc\NodeJs\前端-study-demo\typeorm-demo>ts-node src/test1
Loaded users: [ User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 2, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 3, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 10, firstName: 'sundial', lastName: '3434', age: 20 },
User { id: 11, firstName: 'Timber', lastName: 'Saw', age: 25 },
User { id: 12, firstName: 'Timber', lastName: 'Saw', age: 25 } ]

使用async/await语法

1
2
3
4
5
6
7
8
9
import "reflect-metadata";
import {createConnection} from "typeorm";
import {User} from "./entity/User";
createConnection().then(async connection => {
// 这里可以写实体操作相关的代码
const users = await connection.manager.find(User);
console.log("Loaded users: ", users);

}).catch(error => console.log(error));

使用Repositories

使用Repository来代替EntityManage。每个实体都有自己的repository,可以对这个实体进行任何操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
import "reflect-metadata";
import {createConnection} from "typeorm";
import {User} from "./entity/User";
createConnection().then(async connection => {
// 使用repository
const repository = await connection.getRepository(User);
let user = await repository.findOne(2);
user.firstName="11111111111";
await repository.save(user);
console.log("Loaded users: ", await repository.find());
await repository.remove(user);
process.exit();
}).catch(error => console.log(error));

封装一个Repository

ActiveRecord方式实现

实体:

1
2
3
4
5
6
7
8
9
10
11
12
13
import {Entity, PrimaryGeneratedColumn, Column, BaseEntity} from "typeorm";

@Entity("user")
export class Person extends BaseEntity{
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column()
age: number;
}

操作:

1
2
3
4
5
6
7
import "reflect-metadata";
import {createConnection} from "typeorm";
import {Person} from "./entity/Person";
createConnection().then(async connection => {
console.log("Loaded users: ", await Person.findOne(2));
process.exit();
}).catch(error => console.log(error));

结果:

1
2
D:\mydoc\NodeJs\前端-study-demo\typeorm-demo>ts-node src/test2
Loaded users: Person { id: 2, firstName: 'Timber', lastName: 'Saw', age: 25 }

Caching queries (缓存查询)

它缓存的实现,主要依赖于,自己在数据库里面建立一张缓存表,用的不是内存缓存,这样有好有坏吧,和我们的业务场景其实是不太适用的。这样做的好处,就是它不会把我们的内存跑满,而且也能大幅提高查询速度。但是我们更希望的是一个内存缓存。

它的用法,就是在数据库配置的过程中,增加一个cache字段。”cache”: true,

可以在这个方法中使用:getMany, getOne, getRawMany, getRawOne and getCount find, findAndCount, findByIds, and count

使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const users = await connection
.createQueryBuilder(User, "user")
.where("user.isAdmin = :isAdmin", { isAdmin: true })
.cache(true)
.getMany();


const users = await connection
.getRepository(User)
.find({
where: { isAdmin: true },
cache: true
});


const users = await connection
.createQueryBuilder(User, "user")
.where("user.isAdmin = :isAdmin", { isAdmin: true })
.cache(60000) // 设置缓存时间 1 minute
.getMany();

//设置缓存时间
{
cache: {
duration: 30000 // 30 seconds
}
}

const users = await connection
.createQueryBuilder(User, "user")
.where("user.isAdmin = :isAdmin", { isAdmin: true })
.cache("users_admins", 25000)//缓存名称和时间
.getMany();

也可以用redis当作缓存模块,这样也是比较不错的做法。

1
2
3
4
5
6
7
8
9
10
11
12
{
type: "mysql",
host: "localhost",
...
cache: {
type: "redis",
options: {
host: "localhost",
port: 6379
}
}
}

对应关系

一对一

要与另一个类创建一对一的关系:

其他参考:https://typeorm.io

配置

基本配置参数

1
2
3
4
5
6
type: 当前要连接的数据库类型(mysql,mssql, moongo, ...)
host: 数据库服务暴露的host
port: 数据库服务暴露的端口
username: 连接数据库服务的用户名
password: 连接数据库服务的用户名
database: 连接数据库名

额外参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: 设置连接名,连多个数据库的时候,需要获取连接的时候可用 getConnection('name') 的方式取得对应的数据库连接;
entities: 要加载并用于此连接的实体。写法类似:
entities: [Post, Category, "entity/*.js", "modules/**/entity/*.js"]
entityPrefix: 表名前缀
extra: 拓展的参数值, 如设置连接池连接数量等;
logging:
查询(query):记录所有查询。
错误(error):记录所有失败的查询和错误。
模式(schema):记录模式构建过程。
警告(warn):记录内部orm警告。
记录(info):内部orm信息信息。
日志(log):记录内部orm日志消息。

如果只是简单的打印基本的数据库操作(包含错误)则设置 logging: true
如果要打印以上全部类型,则设置 logging: 'all'
如果只是打印其中的1~2个(如query, error),则可设置 logging: ['query', 'error']

logger: 可设置的类型有 advanced-console,simple-console,file, debug, 在开启 logging 的情况下,logger默认使用类型是 advanced-console, 这个模式会高亮字体和标示sql语句。
cache: 对查询的 sql 结果进行缓存。 支持数据库或者redis。看了下api,redis更为方便一点。
cache: {
type: 'redis', // 必须参数
options: {
host: 'localhost',
port: 6379,
username: '',
password:'',
db: 1,
}
}

创建多个数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[{
"name": "dev",
"type": "mysql",
"host": "172.16.3.34",
"port": 3306,
"username": "root",
"password": "root",
"database": "sonar",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
},{
"name": "pro",
"type": "mysql",
"host": "172.16.3.34",
"port": 3306,
"username": "root",
"password": "root",
"database": "sonar",
"synchronize": true,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"logging": true, // 开启所有数据库信息打印
"logger": "advanced-console", // 高亮字体的打印信息,
"extra": {
"connectionLimit": 10, // 连接池最大连接数量, 查阅资料 建议是 core number * 2 + n
}
}]

获取指定数据库连接:

1
2
3
4
5
6
7
8
9
10
11
方式一:
import { getConnection } from 'typeorm';

const dev_connection = getConnection('dev');
const dev_connection = getConnection('dev');

方式二:
import { getConnectionManager } from "typeorm";

const dev_connection = getConnectionManager().get('dev');
const dev_connection = getConnectionManager().get('pro');

使用 QueryBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "reflect-metadata";
import {createConnection, getConnectionOptions,getManager} from "typeorm";
import {User} from "./entity/User";
//import {dev} from "./MyConnectionInfo";
const myConn = require("./MyConnectionInfo");

async function testConn(){
const connectionOptions = await getConnectionOptions("dev");
const dev = await createConnection(connectionOptions);
let user = await dev
.getRepository(User)
.createQueryBuilder("user") // 第一个参数是别名
.where("user.id = 10")
.orderBy("user.id", "DESC")
.skip(0)
.take(10)
.getMany();
console.log(user);

process.exit();
}
testConn();

附录:

列数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//主键自动生成列类型,主要为整型、小数等数值类型
export type PrimaryGeneratedColumnType = "int" // mysql, mssql, oracle, sqlite
|"int2" // postgres, sqlite
|"int2" // postgres, sqlite
|"int4" // postgres
|"int8" // postgres, sqlite
|"integer" // postgres, oracle, sqlite
|"tinyint" // mysql, mssql, sqlite
|"smallint" // mysql, postgres, mssql, oracle, sqlite
|"mediumint" // mysql, sqlite
|"bigint" // mysql, postgres, mssql, sqlite
|"dec" // oracle, mssql
|"decimal" // mysql, postgres, mssql, sqlite
|"numeric" // postgres, mssql, sqlite
|"number"; // oracle

//带有精度的列类型,主要为各种浮点数、小数类型
export type WithPrecisionColumnType = "float" // mysql, mssql, oracle, sqlite
|"double" // mysql, sqlite
|"dec" // oracle, mssql
|"decimal" // mysql, postgres, mssql, sqlite
|"numeric" // postgres, mssql, sqlite
|"real" // mysql, postgres, mssql, oracle, sqlite
|"double precision" // postgres, oracle, sqlite
|"number" // oracle
|"datetime" // mssql, mysql, sqlite
|"datetime2" // mssql
|"datetimeoffset" // mssql
|"time" // mysql, postgres, mssql
|"time with time zone" // postgres
|"time without time zone" // postgres
|"timestamp" // mysql, postgres, mssql, oracle
|"timestamp without time zone" // postgres
|"timestamp with time zone"; // postgres, oracle

//带有长度的列类型,
export type WithLengthColumnType = "int" // mysql, postgres, mssql, oracle, sqlite
|"tinyint" // mysql, mssql, sqlite
|"smallint" // mysql, postgres, mssql, oracle, sqlite
|"mediumint" // mysql, sqlite
|"bigint" // mysql, postgres, mssql, sqlite
|"character varying" // postgres
|"varying character" // sqlite
|"nvarchar" // mssql
|"character" // mysql, postgres, sqlite
|"native character" // sqlite
|"varchar" // mysql, postgres, mssql, sqlite
|"char" // mysql, postgres, mssql, oracle
|"nchar" // mssql, oracle, sqlite
|"varchar2" // oracle
|"nvarchar2" // oracle, sqlite
|"binary" // mssql
|"varbinary"; // mssql

//简单列类型
export type SimpleColumnType =
//简单数组,为typeorm特有,对饮数据库中string类型
"simple-array" // typeorm-specific, automatically mapped to string
//string类型,对应数据库中varchar类型
|"string" // typeorm-specific, automatically mapped to varchar depend on platform
//数值类型
|"bit" // mssql
|"int2" // postgres, sqlite
|"integer" // postgres, oracle, sqlite
|"int4" // postgres
|"int8" // postgres, sqlite
|"unsigned big int" // sqlite
|"float4" // postgres
|"float8" // postgres
|"smallmoney" // mssql
|"money" // postgres, mssql

//boolean类型
|"boolean" // postgres, sqlite
|"bool" // postgres

//二进制、文本类型
|"tinyblob" // mysql
|"tinytext" // mysql
|"mediumblob" // mysql
|"mediumtext" // mysql
|"blob" // mysql, oracle, sqlite
|"text" // mysql, postgres, mssql, sqlite
|"ntext" // mssql
|"citext" // postgres
|"longblob" // mysql
|"longtext" // mysql
|"bytea" // postgres
|"long" // oracle
|"raw" // oracle
|"long raw" // oracle
|"bfile" // oracle
|"clob" // oracle, sqlite
|"nclob" // oracle
|"image" // mssql

//日期类型
|"timestamp with local time zone" // oracle
|"smalldatetime" // mssql
|"date" // mysql, postgres, mssql, oracle, sqlite
|"interval year" // oracle
|"interval day" // oracle
|"interval" // postgres
|"year" // mysql

//几何类型,只有postgres支持
|"point" // postgres
|"line" // postgres
|"lseg" // postgres
|"box" // postgres
|"circle" // postgres
|"path" // postgres
|"polygon" // postgres

// other types
|"enum" // mysql, postgres
|"cidr" // postgres
|"inet" // postgres
|"macaddr"// postgres
|"bit" // postgres
|"bit varying" // postgres
|"varbit"// postgres
|"tsvector" // postgres
|"tsquery" // postgres
|"uuid" // postgres
|"xml" // mssql, postgres
|"json" // mysql, postgres
|"jsonb" // postgres
|"varbinary" // mssql
|"cursor" // mssql
|"hierarchyid" // mssql
|"sql_variant" // mssql
|"table" // mssql
|"rowid" // oracle
|"urowid" // oracle
|"uniqueidentifier"; // mssql

//列数据类型,为联合类型,各个类型之间可能有重复
export type ColumnType =
//带有精度类型
WithPrecisionColumnType
//带有长度类型
|WithLengthColumnType
//简单类型
|SimpleColumnType
//boolean类型
|BooleanConstructor
//Date类型
|DateConstructor
//数字类型
|NumberConstructor
//字符串类型
|StringConstructor;

Lombok的基本使用

发表于 2019-04-18 | 分类于 Lombok

自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

Lombok实现原理

Lombok就是一个实现了”JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:

1
2
3
4
1. javac对源代码进行分析,生成一棵抽象语法树(AST)
2. javac编译过程中调用实现了JSR 269的Lombok程序
3. 此时Lombok就对第一步骤得到的AST进行处理,找到Lombok注解所在类对应的语法树 (AST),然后修改该语法树(AST),增加Lombok注解定义的相应树节点
4. javac使用修改后的抽象语法树(AST)生成字节码文件

Lombok注解的使用

@Getter/@Setter: 作用类上,生成所有成员变量的getter/setter方法;作用于成员变量上,生成该成员变量的getter/setter方法。可以设定访问权限及是否懒加载等。

@ToString:作用于类,覆盖默认的toString()方法,可以通过of属性限定显示某些字段,通过exclude属性排除某些字段。

@EqualsAndHashCode:作用于类,覆盖默认的equals和hashCode

@NonNull:主要作用于成员变量和参数中,标识不能为空,否则抛出空指针异常。

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor:作用于类上,用于生成构造函数。有staticName、access等属性。

1
2
3
4
staticName属性一旦设定,将采用静态方法的方式生成实例,access属性可以限定访问权限。
@NoArgsConstructor:生成无参构造器;
@RequiredArgsConstructor:生成包含final和@NonNull注解的成员变量的构造器;
@AllArgsConstructor:生成全参构造器

@Data:作用于类上,是以下注解的集合:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor

@Builder:作用于类上,将类转变为建造者模式

@Log:作用于类上,生成日志变量。针对不同的日志实现产品,有不同的注解:

其他重要注解:
@Cleanup:自动关闭资源,针对实现了java.io.Closeable接口的对象有效,如:典型的IO流对象

TypeScript——orm实现

发表于 2019-04-18 | 分类于 Node

基于node,typescript封装,安装步骤略。

新建nodejs项目

npm init

npm install –save mysql

简单封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
'use strict';
const mysql=require('mysql');
module.exports = {
config: {
host: '210.14.139.118',
port: 44257,
database: 'jk_newcenter',
user: 'jk_newcenter',
password: 'aMF3va%d$cc'
},
connection: null,
//创建连接池并连接
openConnection: function (callback) {
this.connection = mysql.createConnection(this.config);
},
closeConnection: function () {
var me = this;
me.connection.end(function (err) {
console.log(err);
});
},

execute: function (config) {
const me = this;
this.openConnection();
me.connection.query(config.sql, config.params, function (err, res) {
if (err) {
console.log(err);
} else if (config.callback) {
config.callback(res);
}
// 关闭数据库连接
me.closeConnection();
});
}
};
完整封装

连接池配置connection.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import * as mysql from "mysql";

const mysqlPool = mysql.createPool({
host: "172.16.3.34",
user: "root",
password: "root",
port: 3306,
database: "sonar",
connectionLimit: 10000
});

const timeout = 4000;

export interface ISqlResults {
results: Array<any>;
fields: Object;
}

interface IQueryObject {
[name: string]: string | number
}

// 实现增删改查基本方法
export const sqlQuery = (sql: mysql.QueryOptions) => new Promise((resolve, reject) =>
mysqlPool.query(sql, (err, results, fields) => err ? reject(err) : resolve(<ISqlResults>{results, fields}))
);

export const and = (dataObject: IQueryObject) => {
let queryString = "";
Object.keys(dataObject).forEach(key => {
queryString += mysql.escape(key) + "=" + dataObject[key] + " and "
});
return queryString.replace(/\sand\s$/, "")
};

export const tableQuery = (table: string, condition: IQueryObject | string) => sqlQuery({
sql: typeof condition === "string" ?
mysql.format("select * from ?? where ??", [table, condition]) :
mysql.format("select * from ?? where ?", [table, condition]),
timeout
});


export const insertTable = (table: string, data: IQueryObject | Array<string | number>) => sqlQuery({
sql: Array.isArray(data) ?
mysql.format("insert into ?? values(??)", [table, (<Array<string | number>>data).join(", ")]) :
mysql.format("insert into ?? set ?", [table, data]),
timeout
});

export const updateTable = (table: string, update: IQueryObject, condition: IQueryObject | string) => sqlQuery({
sql: typeof condition === "string" ?
mysql.format("update ?? set ? where ??", [table, update, condition]) :
mysql.format("update ?? set ? where ?", [table, update, condition]),
timeout
});

export const deleteRow = (table: string, condition: IQueryObject | string) => sqlQuery({
sql: typeof condition === "string" ?
mysql.format("delete from ?? where ??", [table, condition]) :
mysql.format("delete from ?? where ?", [table, condition]),
timeout
});

export default mysqlPool;

ORM核心操作MyOrm.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import { sqlQuery, insertTable, deleteRow, tableQuery, updateTable, ISqlResults } from "./connection";
import { format } from "mysql";

export interface IField {
[name: string]: string | number
}

export interface IError {
err: boolean;
message?: string;
}

export interface IORM<TableType, TableTypeRest> {
fieldMap: IField;
map: IField;
table: string;
defaultValue: TableType;

fetchAll (condition: TableTypeRest | string): Promise<Array<TableType>>;

fetch (query: TableTypeRest | string): Promise<TableType>;

insert (data: TableType): Promise<IError>;

update (data: TableTypeRest, condition: TableTypeRest | string): Promise<IError>;

delete (condition: TableTypeRest | string): Promise<IError>;
}

//ORM framework
export default class MyOrm<Type, TypeRest> implements IORM<Type, TypeRest> {
public fieldMap: IField;
public map: IField;
public table: string;
public defaultValue: Type;

public async delete (condition: TypeRest | string): Promise<IError> {
const dbCondition: IField | string = typeof condition === "string" ? condition :
Object.keys(condition).reduce((acc, curKey) => {acc[this.fieldMap[curKey]] = condition[curKey];
return acc;
}, {});
const { results, fields } = <ISqlResults>await deleteRow(this.table, dbCondition);
return results ? <IError>{ err: false } :
<IError>{
err: true,
message: "you have err on delete from " + this.table
};
}

public async fetch (query: TypeRest | string): Promise<Type> {
const dbQuery: IField = typeof query === "string" ? query :
Object.keys(query).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = query[curKey];
return acc;
}, {});
let { results, fields } = <ISqlResults>await tableQuery(this.table, dbQuery);

results.length || (results = [{}]);
Array.isArray(results) && (results = results[0]);
return <Type>Object.keys(results).reduce((acc, curKey) => {
acc[this.map[curKey]] = results[curKey];
return acc
}, {});
}

public async fetchAll (condition?: TypeRest | string): Promise<Array<Type>> {

const { results, fields } = <ISqlResults>await sqlQuery({
sql: !condition ? format("select * from ??", [this.table]) :
typeof condition === "string" ?
format("select * from ?? where ??", [this.table, condition]) :
format("select * from ?? where ?", [this.table, condition]),
timeout: 2000
});

return <Array<Type>>results.map(value => Object.keys(value).reduce((acc, curKey) => {
acc[this.map[curKey]] = value[curKey];
return acc;
}, {}));
}

public async insert (data: Type): Promise<IError> {
const dbData: IField = Object.keys(data).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = data[curKey];
return acc;
}, {});
const { results } = <ISqlResults>await insertTable(this.table, dbData);
return results ? <IError>{ err: false } : <IError>{ err: true, message: "you have some error in insert data" };
}

public async update (data: TypeRest, condition: TypeRest | string): Promise<IError> {
const dbData: IField = Object.keys(data).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = data[curKey];
return acc;
}, {});
const dbCondition: IField | string = typeof condition === "string" ? condition : Object.keys(condition).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = condition[curKey];
return acc;
}, {});
console.log(dbData,dbCondition);
const { results } = <ISqlResults>await updateTable(this.table, dbData, dbCondition);
return results ? <IError>{ err: false } : { err: true, message: "you have message in update" };
}
}

测试:创建实体:User.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

import ORM from "./MyOrm";

export interface IUserType {
readonly id: number;
readonly firstName: string;
readonly lastName: string;
readonly age: number;
}

export interface IUserTypeRest {
readonly id?: number;
readonly firstName?: string;
readonly lastName?: string;
readonly age?: number;
}
// 只需要继承ORM类即可
export default class User extends ORM<IUserType, IUserTypeRest> {
// 类属性与数据库列字段的映射
public fieldMap = {
id: "id",
firstName: "firstName",
lastName: "lastName",
age: "age"
};
// 默认值
public defaultValue: IUserType = {
id: 0,
firstName: "",
lastName: "",
age: 0
};
public table: string = "user"; // 指定数据库表名

public map = Object.keys(this.fieldMap).reduce((acc, curKey) => {
acc[this.fieldMap[curKey]] = curKey;
return acc;
}, {});
}

测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import User, {IUserType} from "./User";

const user = new User();

async function main() {
const user = new User();
/*await user.insert(<IUserType>{
id: 10,
firstName: "hu",
lastName: "hu",
age: 20
});*/
const data = await user.fetchAll();
console.log(data)
}
main();

ECMAScript 6 async

发表于 2019-04-17 | 分类于 Node

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。

介绍

前文有一个 Generator 函数,依次读取两个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const fs = require('fs');

const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};

const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

上面代码的函数gen可以写成async函数,就是下面这样。

1
2
3
4
5
6
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的改进,体现在以下四点。

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

基本用法

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

例子,指定多少毫秒后输出一个值。

1
2
3
4
5
6
7
8
9
10
11
12
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}

asyncPrint('hello world', 50);

async 函数有多种使用形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 函数声明
async function foo() {}

// 函数表达式
const foo = async function () {};

// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

// Class 的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}

async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// 箭头函数
const foo = async () => {};
语法
返回 Promise 对象

async函数返回一个 Promise 对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。

1
2
3
4
5
6
async function f() {
return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

上面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到。

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

1
2
3
4
5
6
7
8
9
async function f() {
throw new Error('出错了');
}

f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出错了
Promise 对象的状态变化

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

1
2
3
4
5
6
7
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}

getTitle('www.baidu.com').then(console.log);

上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。

await 命令

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

1
2
3
4
5
6
7
8
async function f() {
// 等同于
// return 123;
return await 123;
}

f().then(v => console.log(v))
// 123

另一种情况是,await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Sleep {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) {
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}

/**
* await命令后面是一个Sleep对象的实例。这个实例不是 Promise 对象,但是因为定义了then方法,await会将其视为Promise处理。
*/
(async () => {
const sleepTime = await new Sleep(1000);
console.log(sleepTime);
})();

//JavaScript 一直没有休眠的语法,但是借助await命令就可以让程序停顿指定的时间。
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval);
})
}

// 用法
async function one2FiveInAsync() {
for(let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}

one2FiveInAsync();

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

1
2
3
4
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}

上面代码中,第二个await语句是不会执行的,因为第一个await语句状态变成了reject。

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
async function f() {
try {
await Promise.reject('出错了');
} catch(e) {
}
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world



//或
async function f() {
await Promise.reject('出错了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

TypeScript介绍

发表于 2019-04-17 | 分类于 Node

TypeScript是一个编译到纯JS的有类型定义的JS超集。

TypeScript是一种由微软开发的自由和开源的编程语言。它是javascript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作。TypeScript是为大型应用之开发而设计,而编译时它产生JavaScript 以确保兼容性。[5] TypeScript 支持为已存在的 JavaScript 库添加类型信息的头文件,扩展了它对于流行的库如 jQuery,MongoDB,Node.js 和 D3.js 的好处。

TS遵循当前以及未来出现的ECMAScript规范。TS不仅能兼容现有的JavaScript 代码,它也拥有兼容未来版本的JavaScript的能力。大多数TS的新增特性 都是基于未来的JavaScript提案,这意味着许多TS代码在将来很有可能会变成ECMA的标准.

获取TypeScript

命令行的TypeScript编译器可以使用Node.js包来安装。

1
2
3
4
5
6
7
8
9
10
安装npm install -g typescript

新建文件helloworld.ts
var message:string = "Hello World"
console.log(message)

编译
$tsc helloworld.ts
$ node test.js
Hello World

TypeScript在node项目中的实践

ts-node 环境变量

全局安装 typescript:npm install -g typescript  
  
全局安装 ts-node:npm install -g ts-node

安装它的原因是typescript自带的tsc命令并不能直接运行typescript代码。但值得注意的是 ts-node 并不等于 typescript 的 Node.js ,仅仅封装了 typescript 的编译过程,提供直接运行typescript代码的能力。

配置 ts-node 环境变量:npm config get prefix获取全局位置。

之后编写文件 直接就可以运行:ts-node 文件名.ts

WebStorm自动编译TypeScript

Program:D:\Program Files\nodejs\node_global\tsc

Arguments:–sourcemap –target “ES5”

Output paths to refresh:$FileNameWithoutExtension$.js:$FileNameWithoutExtension$.js.map

Working directory:$FileDir$

新版本的WebStorm已经提供自动编译的功能了,只是需要设置一下。

webstorm中直接运行ts(TypeScript)

安装插件ts-node

ECMAScript 6 Generator

发表于 2019-04-16 | 分类于 Node

Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

Generator 就是一个异步操作的容器。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

1
2
3
4
5
6
7
8
9
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}

var hw = helloWorldGenerator();

console.log(hw.next());

调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

1
2
3
4
5
6
7
8
9
10
11
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
yield表达式

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

  • yield表达式与 Iterator 接口的关系 *

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。

1
2
3
4
5
6
7
8
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};

[...myIterable] // [1, 2, 3]
next 方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

//通过next方法的参数,向 Generator 函数内部输入值的例子。
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}

let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b

如果想要第一次调用next方法时,就能够输入值,可以在 Generator 函数外面再包一层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function wrapper(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}

const wrapped = wrapper(function* () {
console.log(`First input: ${yield}`);
return 'DONE';
});

wrapped().next('hello!')
// First input: hello!

上面代码中,Generator 函数如果不用wrapper先包一层,是无法第一次调用next方法,就输入参数的。

for…of

for…of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}

for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5

利用 Generator 函数和for…of循环,实现斐波那契数列的例子。

1
2
3
4
5
6
7
8
9
10
11
12
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}

for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
yield* 表达式

如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function* foo() {
yield 'a';
yield 'b';
}

function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}

for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y

ES6 提供了yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}

// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}

// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}

for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
作为对象属性的 Generator 函数

如果一个对象的属性是 Generator 函数,可以简写成下面的形式。

1
2
3
4
5
let obj = {
* myGeneratorMethod() {
···
}
};

上面代码中,myGeneratorMethod属性前面有一个星号,表示这个属性是一个 Generator 函数。

它的完整形式如下,与上面的写法是等价的。

1
2
3
4
5
let obj = {
myGeneratorMethod: function* () {
// ···
}
};

生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};
var f = F.call(obj);

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

obj.a // 1
obj.b // 2
obj.c // 3

首先是F内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。

上面代码中,执行的是遍历器对象f,但是生成的对象实例是obj,有没有办法将这两个对象统一呢?

一个办法就是将obj换成F.prototype。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var f = F.call(F.prototype);

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

再将F改成构造函数,就可以对它执行new命令了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}

function F() {
return gen.call(gen.prototype);
}

var f = new F();

f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。

(1)异步操作的同步化表达

Ajax 是典型的异步操作,通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}

function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}

var it = main();
it.next();

上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。

下面是另一个例子,通过 Generator 函数逐行读取文本文件。

1
2
3
4
5
6
7
8
9
10
function* numbers() {
let file = new FileReader("numbers.txt");
try {
while(!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
}

上面代码打开文本文件,使用yield表达式可以手动逐行读取文件。

(2)控制流管理
如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。

1
2
3
4
5
6
7
8
9
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});

采用 Promise 改写上面的代码。

1
2
3
4
5
6
7
8
9
10
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();

上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator 函数可以进一步改善代码运行流程。

1
2
3
4
5
6
7
8
9
10
11
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}

函数的异步应用

ES6 诞生以前,异步编程的方法,大概有下面四种。

回调函数 事件监听 发布/订阅 Promise 对象

回调
1
2
3
4
fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
if (err) throw err;
console.log(data);
});
Promise

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。

1
2
3
4
5
fs.readFile(fileA, 'utf-8', function (err, data) {
fs.readFile(fileB, 'utf-8', function (err, data) {
// ...
});
});

Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用 Promise,连续读取多个文件,写法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
console.log(data.toString());
})
.then(function () {
return readFile(fileB);
})
.then(function (data) {
console.log(data.toString());
})
.catch(function (err) {
console.log(err);
});

使用了fs-readfile-promise模块,它的作用就是返回一个 Promise 版本的readFile函数。Promise 提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。

可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。

Generator 函数的流程管理
1
2
3
4
5
6
7
8
9
10
11
function* gen() {
// ...
}

var g = gen();
var res = g.next();

while(!res.done){
console.log(res.value);
res = g.next();
}

但是,这不适合异步操作。如果必须保证前一步执行完,才能执行后一步,上面的自动执行就不可行。这时,Thunk 函数就能派上用处。以读取文件为例。下面的 Generator 函数封装了两个异步操作。

1
2
3
4
5
6
7
8
9
10
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function* (){
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};

上面代码中,yield命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
var g = gen();

var r1 = g.next();
r1.value(function (err, data) {
if (err) throw err;
var r2 = g.next(data);
r2.value(function (err, data) {
if (err) throw err;
g.next(data);
});
});

变量g是 Generator 函数的内部指针,表示目前执行到哪一步。next方法负责将指针移动到下一步,并返回该步的信息(value属性和done属性)。

Thunk 函数的自动流程管理

Thunk 函数真正的威力,在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function run(fn) {
var gen = fn();

function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}

next();
}

function* g() {
// ...
}

run(g);

上面代码的run函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出。

有了这个执行器,执行 Generator 函数方便多了。不管内部有多少个异步操作,直接把 Generator 函数传入run函数即可。当然,前提是每一个异步操作,都要是 Thunk 函数,也就是说,跟在yield命令后面的必须是 Thunk 函数。

1
2
3
4
5
6
7
8
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};

run(g);

上面代码中,函数g封装了n个异步的读取文件操作,只要执行run函数,这些操作就会自动完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行。

Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是,必须有一种机制,自动控制 Generator 函数的流程,接收和交还程序的执行权。回调函数可以做到这一点,Promise 对象也可以做到这一点。

co 模块

co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。

1
2
3
4
5
6
7
8
9
10
var gen = function* () {
//用于依次读取两个文件。
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
//co 模块可以让你不用编写 Generator 函数的执行器。
var co = require('co');
co(gen);

co函数返回一个Promise对象,因此可以用then方法添加回调函数。

1
2
3
co(gen).then(function (){
console.log('Generator 函数执行完成');
});

** co 模块的原理 **
为什么 co 可以自动执行 Generator 函数?

前面说过,Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。两种方法可以做到这一点。

(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。

(2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。

基于 Promise 对象的自动执行器

还是沿用上面的例子。首先,把fs模块的readFile方法包装成一个 Promise 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require('fs');

var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};

var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

然后,手动执行上面的 Generator 函数。

1
2
3
4
5
6
7
var g = gen();

g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
});
});

手动执行其实就是用then方法,层层添加回调函数。理解了这一点,就可以写出一个自动执行器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function run(gen){
var g = gen();

function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}

next();
}

run(gen);

co 模块的源码分析

co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。

这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数组的写法
co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
];
console.log(res);
}).catch(onerror);

// 对象的写法
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
};
console.log(res);
}).catch(onerror);

例子。

1
2
3
4
5
6
7
8
9
co(function* () {
var values = [n1, n2, n3];
yield values.map(somethingAsync);
});

function* somethingAsync(x) {
// do something async
return y
}

上面的代码允许并发三个somethingAsync异步操作,等到它们全部完成,才会进行下一步。

上一页1…456…25下一页
初晨

初晨

永远不要说你知道本质,更别说真相了。

249 日志
46 分类
109 标签
近期文章
  • WebSocket、Socket、TCP、HTTP区别
  • Springboot项目的接口防刷
  • 深入理解Volatile关键字及其实现原理
  • 使用vscode搭建个人笔记环境
  • HBase介绍安装与操作
© 2018 — 2020 Copyright
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4