Skip to content

Commit c14d07c

Browse files
authored
feat(hooks): Performance improvement (#106)
1 parent 2490de6 commit c14d07c

File tree

6 files changed

+126
-158
lines changed

6 files changed

+126
-158
lines changed

main/hooks/src/base.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AsyncMiddleware } from './compose.ts';
2-
import { copyProperties, copyToSelf } from './utils.ts';
2+
import { copyProperties } from './utils.ts';
33

44
export const HOOKS: string = Symbol('@feathersjs/hooks') as any;
55

@@ -23,9 +23,9 @@ export interface HookContext<T = any, C = any> extends BaseHookContext<C> {
2323
arguments: any[];
2424
}
2525

26-
export type HookContextConstructor = new (
27-
data?: { [key: string]: any },
28-
) => BaseHookContext;
26+
export type HookContextConstructor = new (data?: {
27+
[key: string]: any;
28+
}) => BaseHookContext;
2929

3030
export type HookDefaultsInitializer = (
3131
self?: any,
@@ -136,8 +136,6 @@ export class HookManager {
136136
const ContextClass = class ContextClass extends Base {
137137
constructor(data: any) {
138138
super(data);
139-
140-
copyToSelf(this);
141139
}
142140
};
143141
const params = this.getParams();

main/hooks/src/utils.ts

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,15 @@
1-
const proto = Object.prototype as any;
2-
// These are non-standard but offer a more reliable prototype based
3-
// lookup for properties
4-
const hasProtoDefinitions = typeof proto.__lookupGetter__ === 'function' &&
5-
typeof proto.__defineGetter__ === 'function' &&
6-
typeof proto.__defineSetter__ === 'function';
7-
8-
export function copyToSelf(target: any) {
9-
// tslint:disable-next-line
10-
for (const key in target) {
11-
if (!Object.hasOwnProperty.call(target, key)) {
12-
const getter = hasProtoDefinitions
13-
? target.constructor.prototype.__lookupGetter__(key)
14-
: Object.getOwnPropertyDescriptor(target, key);
15-
16-
if (hasProtoDefinitions && getter) {
17-
target.__defineGetter__(key, getter);
18-
19-
const setter = target.constructor.prototype.__lookupSetter__(key);
20-
21-
if (setter) {
22-
target.__defineSetter__(key, setter);
23-
}
24-
} else if (getter) {
25-
Object.defineProperty(target, key, getter);
26-
} else {
27-
target[key] = target[key];
28-
}
29-
}
30-
}
31-
}
32-
331
export function copyProperties<F>(target: F, ...originals: any[]) {
342
for (const original of originals) {
35-
const originalProps = (Object.keys(original) as any)
36-
.concat(Object.getOwnPropertySymbols(original));
3+
const originalProps = (Object.keys(original) as any).concat(
4+
Object.getOwnPropertySymbols(original),
5+
);
376

387
for (const prop of originalProps) {
398
const propDescriptor = Object.getOwnPropertyDescriptor(original, prop);
409

4110
if (
42-
propDescriptor && !Object.prototype.hasOwnProperty.call(target, prop)
11+
propDescriptor &&
12+
!Object.prototype.hasOwnProperty.call(target, prop)
4313
) {
4414
Object.defineProperty(target, prop, propDescriptor);
4515
}

main/hooks/test/benchmark.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ let threshold: number;
2020

2121
(async () => {
2222
baseline = await getRuntime(() => hello('Dave'));
23-
threshold = baseline * 25; // TODO might be improved further
23+
threshold = baseline * 10;
2424
})();
2525

2626
it('empty hook', async () => {
@@ -57,7 +57,9 @@ it('single hook, withParams and props', async () => {
5757
async (_ctx: HookContext, next: NextFunction) => {
5858
await next();
5959
},
60-
]).params('name').props({ dave: true }),
60+
])
61+
.params('name')
62+
.props({ dave: true }),
6163
);
6264

6365
const runtime = await getRuntime(() => hookHello('Dave'));

main/hooks/test/class.test.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { assertEquals, assertStrictEquals, it } from './dependencies.ts';
2-
import { HookContext, hooks, middleware, NextFunction, WrapperAddon } from '../src/index.ts';
2+
import { HookContext, hooks, middleware, NextFunction } from '../src/index.ts';
33

44
interface Dummy {
55
sayHi(name: string): Promise<string>;
@@ -24,17 +24,10 @@ it('hooking object on class adds to the prototype', async () => {
2424
hooks(DummyClass, {
2525
sayHi: middleware([
2626
async (ctx: HookContext, next: NextFunction) => {
27-
const sayHi = DummyClass.prototype.sayHi as any as WrapperAddon<any>;
28-
29-
assertEquals(
30-
ctx,
31-
new sayHi.Context({
32-
arguments: ['David'],
33-
method: 'sayHi',
34-
name: 'David',
35-
self: instance,
36-
}),
37-
);
27+
assertEquals(ctx.arguments, ['David']);
28+
assertEquals(ctx.method, 'sayHi');
29+
assertEquals(ctx.name, 'David');
30+
assertEquals(ctx.self, instance);
3831

3932
await next();
4033

@@ -79,14 +72,9 @@ it('works with inheritance', async () => {
7972
const DummyClass = createDummyClass();
8073

8174
const first = async (ctx: HookContext, next: NextFunction) => {
82-
assertEquals(
83-
ctx,
84-
new (OtherDummy.prototype.sayHi as any).Context({
85-
arguments: ['David'],
86-
method: 'sayHi',
87-
self: instance,
88-
}),
89-
);
75+
assertEquals(ctx.arguments, ['David']);
76+
assertEquals(ctx.method, 'sayHi');
77+
assertEquals(ctx.self, instance);
9078

9179
await next();
9280

@@ -128,8 +116,7 @@ it('works with multiple context updaters', async () => {
128116
]).params('name'),
129117
});
130118

131-
class OtherDummy extends DummyClass {
132-
}
119+
class OtherDummy extends DummyClass {}
133120

134121
hooks(OtherDummy, {
135122
sayHi: middleware([

main/hooks/test/function.test.ts

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,7 @@ it('deleting context.result, does not skip method call', async () => {
118118
await next();
119119
};
120120

121-
const fn = hooks(
122-
hello,
123-
middleware([
124-
updateResult,
125-
deleteResult,
126-
]),
127-
);
121+
const fn = hooks(hello, middleware([updateResult, deleteResult]));
128122
const res = await fn('There');
129123

130124
assertStrictEquals(res, 'There');
@@ -169,27 +163,33 @@ it('uses hooks from context object and its prototypes', async () => {
169163
const o1 = { message: 'Hi' };
170164
const o2 = Object.create(o1);
171165

172-
setMiddleware(o1, [async (ctx: HookContext, next: NextFunction) => {
173-
ctx.arguments[0] += ' o1';
166+
setMiddleware(o1, [
167+
async (ctx: HookContext, next: NextFunction) => {
168+
ctx.arguments[0] += ' o1';
174169

175-
await next();
176-
}]);
170+
await next();
171+
},
172+
]);
177173

178-
setMiddleware(o2, [async (ctx, next) => {
179-
ctx.arguments[0] += ' o2';
174+
setMiddleware(o2, [
175+
async (ctx, next) => {
176+
ctx.arguments[0] += ' o2';
180177

181-
await next();
182-
}]);
178+
await next();
179+
},
180+
]);
183181

184182
o2.sayHi = hooks(
185183
async function (this: any, name: string) {
186184
return `${this.message} ${name}`;
187185
},
188-
middleware([async (ctx, next) => {
189-
ctx.arguments[0] += ' fn';
186+
middleware([
187+
async (ctx, next) => {
188+
ctx.arguments[0] += ' fn';
190189

191-
await next();
192-
}]),
190+
await next();
191+
},
192+
]),
193193
);
194194

195195
const res = await o2.sayHi('Dave');
@@ -283,7 +283,9 @@ it('assigns props to context', async () => {
283283

284284
await next();
285285
},
286-
]).params('name').props({ dev: true }),
286+
])
287+
.params('name')
288+
.props({ dev: true }),
287289
);
288290

289291
assertStrictEquals(await fn('Dave'), 'Hello Changed');
@@ -292,19 +294,22 @@ it('assigns props to context', async () => {
292294
it('assigns props to context by options', async () => {
293295
const fn = hooks(
294296
hello,
295-
middleware([
296-
async (ctx, next) => {
297-
assertStrictEquals(ctx.name, 'Dave');
298-
assertStrictEquals(ctx.dev, true);
299-
300-
ctx.name = 'Changed';
301-
302-
await next();
297+
middleware(
298+
[
299+
async (ctx, next) => {
300+
assertStrictEquals(ctx.name, 'Dave');
301+
assertStrictEquals(ctx.dev, true);
302+
303+
ctx.name = 'Changed';
304+
305+
await next();
306+
},
307+
],
308+
{
309+
params: ['name'],
310+
props: { dev: true },
303311
},
304-
], {
305-
params: ['name'],
306-
props: { dev: true },
307-
}),
312+
),
308313
);
309314

310315
assertStrictEquals(await fn('Dave'), 'Hello Changed');
@@ -443,7 +448,6 @@ it('context has own properties', async () => {
443448

444449
assert(keys.includes('self'));
445450
assert(keys.includes('message'));
446-
assert(keys.includes('name'));
447451
assert(keys.includes('arguments'));
448452
assert(keys.includes('result'));
449453
});
@@ -468,12 +472,14 @@ it('creates context with default params', async () => {
468472

469473
await next();
470474
},
471-
]).params('name', 'params').defaults(() => {
472-
return {
473-
name: 'Bertho',
474-
params: {},
475-
};
476-
}),
475+
])
476+
.params('name', 'params')
477+
.defaults(() => {
478+
return {
479+
name: 'Bertho',
480+
params: {},
481+
};
482+
}),
477483
);
478484

479485
assertStrictEquals(await fn('Dave'), 'Hello Dave');

0 commit comments

Comments
 (0)