qrcode

马上订阅,开启修仙之旅

RxDB 异常机制

RxDB 是一个由 JavaScript 实现,拥有响应式,离线优先等特性的数据库。它拥有以下特性:

  • 多平台支持:浏览器、Node.js、Electron、Cordova、React-Native 和其它 JavaScript 运行时;
  • 基于 RxJS 的响应式数据处理;
  • 支持客户端与服务端之间的数据备份,兼容 PouchDB,CouchDB 和 IBM Cloudant;
  • 基于易于学习的 jsonschema 标准;
  • 提供 mongoDB 和 mongoose 类似的查询方式;
  • 支持单个字段的加密,从而保护用户的隐私;
  • 支持导入/导入 JSON 数据,非常适合 TDD
  • 支持不同浏览器窗口或 Node.js 进程之间数据同步;
  • 支持 ORM。

接下来本文将基于 RxDB 的 7.5.1 版本,简单分析一下 RxDB 内部的异常机制。

基础知识

JavaScript 异常类型:

  • EvalError —— 创建一个error实例,表示错误的原因:与 eval() 有关。
  • InternalError —— 创建一个代表Javascript引擎内部错误的异常抛出的实例。 如: “递归太多”。
  • RangeError —— 创建一个error实例,表示错误的原因:数值变量或参数超出其有效范围。
  • ReferenceError —— 创建一个error实例,表示错误的原因:无效引用。
  • SyntaxError —— 创建一个error实例,表示错误的原因:eval()在解析代码的过程中发生的语法错误。
  • TypeError —— 创建一个error实例,表示错误的原因:变量或参数不属于有效类型。
  • URIError —— 创建一个error实例,表示错误的原因:给 encodeURI()decodeURl()传递的参数无效。

异常对象分类

在 RxDB 内部主要定义了两个异常类:

  • RxError:继承于 Error 类
  • RxTypeError:继承于 TypeError 类

利用 rx-error.js 文件提供的 newRxError 和 newRxTypeError 方法,我们可以方便地抛出自定义异常。在 encryption 插件内部,若数据库字段加密的密码的长度不为字符串或长度小于 8 时,则会抛出对应的异常。

具体的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const overwritable = {
validatePassword: function(password) {
if (password && typeof password !== 'string') {
throw RxError.newRxTypeError('EN1', {
password
});
}
if (password && password.length < minPassLength) {
throw RxError.newRxError('EN2', {
minPassLength,
password
});
}
}
};

newRxTypeError 函数

这里我们先来分析 RxError.newRxTypeError:

1
2
export const newRxTypeError = (code, parameters) => 
new RxTypeError(code, overwritable.tunnelErrorMessage(code), parameters);

其中 RxTypeError 继承于 TypeError,该类的实现如下:

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
/* 
* TypeError(类型错误)对象用来表示值的类型非预期类型时发生的错误。
* new TypeError([message[, fileName[, lineNumber]]])
* message:错误描述
* fileName:引起该异常代码所在的文件的名字
* lineNumber:引起该异常的代码的行号
*/
export class RxTypeError extends TypeError {
// 'EN1', { password }
constructor(code, message, parameters = {}) {
const mes = messageForError(message, parameters);
super(mes);
this.code = code;
this.message = mes;
this.parameters = parameters;
this.rxdb = true; // tag them as internal
}
get name() {
return 'RxError';
}
toString() {
return this.message;
}
get typeError() {
return true;
}
};

在 RxTypeError 构造函数内部,会调用 messageForError 函数,生成异常消息:

1
2
3
4
5
const messageForError = (message, parameters) => {
return 'RxError:' + '\n' +
message + '\n' +
parametersToString(parameters);
};

在 messageForError 函数内部,会进一步调用 parametersToString 函数进行参数对象

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
/**
* transform an object of parameters to a presentable string
* @param {any} parameters
* @return {string}
*/
const parametersToString = (parameters) => {
// { minPassLength, password }
let ret = '';
if (Object.keys(parameters).length === 0)
return ret;
ret += 'Given parameters: {\n';
ret += Object.keys(parameters) // [minPassLength, password]
.map(k => {
let paramStr = '[object Object]';
try {
paramStr = JSON.stringify(
parameters[k],
(k, v) => v === undefined ? null : v,
2
);
} catch (e) {}
return k + ':' + paramStr;
})
.join('\n');
ret += '}';
return ret;
};

JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON 字符串,如果指定了 replacer 是一个函数,则可以替换值,或者如果指定了 replacer 是一个数组,可选的仅包括指定的属性。

关于序列化,有下面五点注意事项:

  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中。
  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
  • 不可枚举的属性会被忽略。
1
2
3
4
5
6
7
8
9
10
11
JSON.stringify({});                        // '{}'
JSON.stringify(true); // 'true'
JSON.stringify("foo"); // '"foo"'
JSON.stringify([1, "false", false]); // '[1,"false",false]'
JSON.stringify({ x: 5 }); // '{"x":5}'

JSON.stringify({x: 5, y: 6});
// "{"x":5,"y":6}"

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// '[1,"false",false]'

分析完 RxError.newRxTypeError,我们再来看一下 newRxError 函数。

newRxError 函数

1
2
export const newRxError = (code, parameters) => 
new RxError(code, overwritable.tunnelErrorMessage(code), parameters);

RxError 类继承于 Error 类,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class RxError extends Error {
constructor(code, message, parameters = {}) {
const mes = messageForError(message, parameters);
super(mes);
this.code = code;
this.message = mes;
this.parameters = parameters;
this.rxdb = true; // tag them as internal
}
get name() {
return 'RxError';
}
toString() {
return this.message;
}
get typeError() {
return false;
}
};

另外在创建异常消息时,内部会调用 overwritable.tunnelErrorMessage() 方法生成 code 对应的异常消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
 /**
* overwritte to map error-codes to text-messages
* @param {string} message
* @return {string}
*/
tunnelErrorMessage(message) {
// TODO better text with link
return `RxDB Error-Code ${message}.
- To find out what this means, use the error-messages-plugin
https://pubkey.github.io/rxdb/custom-build.html#error-messages
- Or search for this code https://github.com/pubkey/rxdb/search?
l=JavaScript&q=${message}%3A`;
}

不知道小伙伴有没有注意到 ‘EN1’ 和 ‘EN2’ 异常码,这些异常代码统一的定义在 error-message.js 文件的 CODES 对象中,该对象统一定义了 RxDB 中的所有异常信息:

1
2
3
4
5
6
7
8
9
const CODES = {
// util.js
UT1: 'given name is no string or empty',
UT2: `collection- and database-names must match the regex`

// plugins/encryption.js
EN1: 'password is no string',
EN2: 'validatePassword: min-length of password not complied',
}

此外在 error-message 插件内部会重写默认的 tunnelErrorMessage 方法,从而正常的显示异常消息:

1
2
3
4
5
6
7
8
9
export const overwritable = {
tunnelErrorMessage(code) {
if (!CODES[code]) {
console.error('RxDB: Error-Code not known: ' + code);
throw new Error('Error-Cdoe ' + code + ' not known, contact the maintainer');
}
return CODES[code];
}
};

最后再总结一下异常的处理流程:

  • 调用 RxError 对象的 newRxTypeError() 方法,创建异常内置的异常对象。例如:RxError.newRxTypeError('EN1', { password })
  • 在 newRxTypeError 函数内部,会调用 overwritable.tunnelErrorMessage(code) 方法,根据异常代码,找出对应的异常信息(异常信息按照功能进行分类,定义在 error-message.js 文件内)。
  • 之后调用 RxTypeError 构造函数,创建 RxTypeError 对象。在 RxTypeError 构造函数内部,会对传入的参数对象进行序列化处理,然后与 code 对应的错误信息进行拼接,最终生成完整异常信息。

总结

  • 在 RxDB 内部主要定义了两个异常类:RxError 和 RxTypeError,并提供了 newRxTypeError 和 newRxError 两个函数,用于快速创建异常对象。

  • RxDB 异常处理机制,根据功能对异常代码进行分类,从而实现异常信息的统一管理。常见模块对应的异常代码映射关系如下:

    • UT —— util.js
    • P —— pouch-db.js
    • QU —— rx-query.js
    • MQ —— mquery.js
    • DB —— rx-database
    • COL —— rx-collection
    • EN —— plugins/encryption.js

欢迎小伙伴们订阅前端修仙之路,及时阅读 Angular、RxJS、TypeScript 和 Node.js 最新文章。

qrcode