Skip to content

Commit bd7abbc

Browse files
authored
Merge pull request #43 from ReactiveDB/fix/rdb-exception
refactor(assert): eliminate costly exception creation when an assertion is met
2 parents 6ff34ff + 6a4dc7c commit bd7abbc

File tree

9 files changed

+68
-55
lines changed

9 files changed

+68
-55
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"tslint": "^5.9.1",
9090
"tslint-eslint-rules": "^5.1.0",
9191
"tslint-loader": "^3.6.0",
92-
"typescript": "^2.8.3",
92+
"typescript": "^3.0.1",
9393
"webpack": "^4.1.1",
9494
"webpack-cli": "^2.0.10",
9595
"webpack-dev-server": "^3.1.0"

src/exception/Exception.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
1-
export interface ReactiveDBException extends Error { }
1+
export class ReactiveDBException extends Error {
22

3-
export interface ReactiveDBExceptionCtor {
4-
new(message: string): ReactiveDBException
5-
readonly prototype: ReactiveDBException
6-
}
7-
8-
function ReactiveDBExceptionCtor(this: ReactiveDBException, message: string): ReactiveDBException {
9-
const err = Error.call(this, message)
10-
this.name = err.name
11-
this.message = message
12-
this.stack = err.stack
13-
return this
14-
}
15-
16-
ReactiveDBExceptionCtor.prototype = Object.create(Error.prototype, {
17-
constructor: {
18-
value: ReactiveDBExceptionCtor,
19-
enumerable: false,
20-
writable: true,
21-
configurable: true
3+
constructor(message: string) {
4+
super(message)
5+
this.name = 'ReactiveDBError';
6+
(Object as any).setPrototypeOf(this, ReactiveDBException.prototype)
227
}
23-
})
248

25-
export const ReactiveDBException = ReactiveDBExceptionCtor as any as ReactiveDBExceptionCtor
9+
}

src/storage/Database.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class Database {
5151

5252
private findSchema = (name: string): ParsedSchema => {
5353
const schema = this.schemas.get(name)
54-
return assertValue(schema, Exception.NonExistentTable(name))
54+
return assertValue(schema, Exception.NonExistentTable, name)
5555
}
5656

5757
/**
@@ -60,11 +60,11 @@ export class Database {
6060
*/
6161
defineSchema<T>(tableName: string, schema: SchemaDef<T>) {
6262
const advanced = !this.schemaDefs.has(tableName) && !this.connected
63-
assert(advanced, Exception.UnmodifiableTable())
63+
assert(advanced, Exception.UnmodifiableTable)
6464

6565
const hasPK = Object.keys(schema)
6666
.some((key: string) => schema[key].primaryKey === true)
67-
assert(hasPK, Exception.PrimaryKeyNotProvided())
67+
assert(hasPK, Exception.PrimaryKeyNotProvided)
6868

6969
this.schemaDefs.set(tableName, schema)
7070
return this
@@ -101,7 +101,7 @@ export class Database {
101101
}
102102

103103
load(data: any) {
104-
assert(!this.connected, Exception.DatabaseIsNotEmpty())
104+
assert(!this.connected, Exception.DatabaseIsNotEmpty)
105105

106106
const load = (db: lf.Database) => {
107107
forEach(data.tables, (entities: any[], name: string) => {
@@ -487,7 +487,7 @@ export class Database {
487487
columns.set(key, def.type as RDBType)
488488

489489
if (def.primaryKey) {
490-
assert(!primaryKey[0], Exception.PrimaryKeyConflict())
490+
assert(!primaryKey[0], Exception.PrimaryKeyConflict)
491491
primaryKey.push(key)
492492
}
493493

@@ -649,7 +649,7 @@ export class Database {
649649

650650
const onlyNavigator = Array.from(fieldsValue.keys())
651651
.every(key => contains(key, navigators))
652-
assert(!onlyNavigator, Exception.InvalidQuery())
652+
assert(!onlyNavigator, Exception.InvalidQuery)
653653

654654
if (!hasKey) {
655655
// 保证主键一定比关联字段更早的被遍历到
@@ -669,7 +669,7 @@ export class Database {
669669
}
670670

671671
columns.push(...ret.columns)
672-
assert(!rootDefinition[key], Exception.AliasConflict(key, tableName))
672+
assert(!rootDefinition[key], Exception.AliasConflict, key, tableName)
673673

674674
if ((defs as ColumnDef).column) {
675675
rootDefinition[key] = defs
@@ -761,7 +761,7 @@ export class Database {
761761
const schema = this.findSchema(tableName)
762762
const pk = schema.pk
763763
const pkVal = compoundEntites[pk]
764-
assert(pkVal !== undefined, Exception.PrimaryKeyNotProvided())
764+
assert(pkVal !== undefined, Exception.PrimaryKeyNotProvided)
765765

766766
const [ table ] = Database.getTables(db, tableName)
767767
const identifier = fieldIdentifier(tableName, pkVal)

src/storage/modules/Mutation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class Mutation {
6767
}
6868

6969
private toUpdater() {
70-
const meta = assertValue(this.meta, Exception.PrimaryKeyNotProvided())
70+
const meta = assertValue(this.meta, Exception.PrimaryKeyNotProvided)
7171
const query = this.db.update(this.table)
7272
query.where(this.table[meta.key].eq(meta.val))
7373

@@ -84,7 +84,7 @@ export class Mutation {
8484
}
8585

8686
private toRow() {
87-
const meta = assertValue(this.meta, Exception.PrimaryKeyNotProvided())
87+
const meta = assertValue(this.meta, Exception.PrimaryKeyNotProvided)
8888
return {
8989
table: this.table,
9090
row: this.table.createRow({

src/storage/modules/QueryToken.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class QueryToken<T> {
3939
}
4040

4141
values(): Observable<T[]> {
42-
assert(!this.consumed, TokenConsumed())
42+
assert(!this.consumed, TokenConsumed)
4343

4444
this.consumed = true
4545
return this.selector$.pipe(
@@ -49,7 +49,7 @@ export class QueryToken<T> {
4949
}
5050

5151
changes(): Observable<T[]> {
52-
assert(!this.consumed, TokenConsumed())
52+
assert(!this.consumed, TokenConsumed)
5353

5454
this.consumed = true
5555
return this.selector$.pipe(

src/storage/modules/Selector.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ export class Selector <T> {
3232
const [ minSkip ] = skipsAndLimits
3333
const maxLimit = skipsAndLimits.reduce((acc, current) => {
3434
const nextSkip = acc.skip! + acc.limit!
35-
assert(current.skip === nextSkip, Exception.TokenConcatFailed(`
35+
assert(current.skip === nextSkip, Exception.TokenConcatFailed, `
3636
skip should be serial,
3737
expect: ${JSON.stringify(acc, null, 2)}
3838
actual: ${nextSkip}
39-
`))
39+
`)
4040
return current
4141
})
4242
return new Selector(
@@ -60,7 +60,7 @@ export class Selector <T> {
6060
refCount()
6161
)
6262
dist.values = () => {
63-
assert(!dist.consumed, Exception.TokenConsumed())
63+
assert(!dist.consumed, Exception.TokenConsumed)
6464
dist.consumed = true
6565
return from(metaDatas).pipe(
6666
mergeMap(metaData => metaData.values()),
@@ -230,7 +230,7 @@ export class Selector <T> {
230230
)
231231
)
232232
)
233-
assert(equal, Exception.TokenConcatFailed())
233+
assert(equal, Exception.TokenConcatFailed)
234234

235235
return Selector.concatFactory(this, ...selectors)
236236
}

src/utils/assert.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,44 @@
11
import { Truthy } from './truthy'
22

3-
export function assert(condition: boolean, error: Error | string): void {
4-
truthyOrThrow(condition, error)
3+
type FailureHandler<U extends any[]> = (...args: U) => Error
4+
5+
export function assert(condition: boolean, failureMsg: string): void
6+
export function assert<U extends any[]>(
7+
condition: boolean,
8+
failure: FailureHandler<U>,
9+
...failureArgs: U
10+
): void
11+
export function assert<U extends any[]>(
12+
condition: boolean,
13+
failure: FailureHandler<U> | string,
14+
...failureArgs: U
15+
): void {
16+
truthyOrThrow(condition, failure, ...failureArgs)
517
}
618

7-
export function assertValue<T>(
19+
export function assertValue<T>(value: T, falsyValueMsg: string): Truthy<T> | never
20+
export function assertValue<T, U extends any[]>(
21+
value: T,
22+
failure: FailureHandler<U>,
23+
...failureArgs: U
24+
): Truthy<T> | never
25+
export function assertValue<T, U extends any[]>(
826
value: T,
9-
error: Error | string
27+
failure: FailureHandler<U> | string,
28+
...failureArgs: U
1029
): Truthy<T> | never {
11-
return truthyOrThrow(value, error)
30+
return truthyOrThrow(value, failure, ...failureArgs)
1231
}
1332

14-
function truthyOrThrow<T>(x: T, error: Error | string): Truthy<T> | never {
33+
function truthyOrThrow<T, U extends any[]>(
34+
x: T,
35+
failure: FailureHandler<U> | string,
36+
...failureArgs: U
37+
): Truthy<T> | never {
1538
if (x) {
1639
return x as Truthy<T>
1740
}
1841

19-
if (error instanceof Error) {
20-
throw error
21-
} else {
22-
throw new Error(error)
23-
}
42+
const error = typeof failure === 'string' ? new Error(failure) : failure(...failureArgs)
43+
throw error
2444
}

test/specs/utils/utils.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ export default describe('Utils Testcase: ', () => {
381381
describe('Func: assert', () => {
382382

383383
it('should throw when assert failed [1]', () => {
384-
const check = () => assert(false, Error('failed'))
384+
const check = () => assert(false, (msg: string) => new Error(msg), 'failed')
385385
expect(check).to.throw('failed')
386386
})
387387

@@ -391,10 +391,19 @@ export default describe('Utils Testcase: ', () => {
391391
})
392392

393393
it('should not throw when assert successed', () => {
394-
const check = () => assert(true, Error('error code path'))
394+
const check = () => assert(true, 'error code path')
395395
expect(check).to.not.throw()
396396
})
397397

398+
it('should not execute error function when assert condition is met', () => {
399+
let x = 0
400+
assert(true, () => {
401+
x++
402+
return new Error('failed')
403+
})
404+
expect(x).to.equal(0)
405+
})
406+
398407
})
399408

400409
describe('Func: hash', () => {

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7423,9 +7423,9 @@ typescript@^2.4.2, typescript@^2.6.1:
74237423
version "2.6.2"
74247424
resolved "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
74257425

7426-
typescript@^2.8.3:
7427-
version "2.8.3"
7428-
resolved "https://registry.npmjs.org/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170"
7426+
typescript@^3.0.1:
7427+
version "3.0.1"
7428+
resolved "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb"
74297429

74307430
uglify-es@^3.3.4:
74317431
version "3.3.9"

0 commit comments

Comments
 (0)