Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/misc/__test__/keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,24 @@ describe('keys', () => {

expect([...keys(a, b, c, d)]).toEqual(['name', 'age', 'sex', 'role']);
});

test('should handle empty key properly', () => {
const a = { '': 'FooBar' };

expect([...keys(a)]).toEqual(['']);
});

test('should handle reserved keys properly', () => {
const a = { toString: 1 };
const b = { valueOf: 2 };

expect([...keys(a, b)]).toEqual(['toString', 'valueOf']);
});

test('should handle arrays', () => {
const a = [1, 1, 1];
const b = [2, 2, 2, 2, 2];

expect([...keys(a, b)]).toEqual(['0', '1', '2', '3', '4']);
});
});
36 changes: 22 additions & 14 deletions src/misc/keys.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
// Using `Object.prototype.hasOwnProperty` instead of `Object.hasOwn` to support more environments
const hasOwn = Object.prototype.hasOwnProperty;

/**
* Returns generator that iterates over each unique own key of all given objects.
* Returns generator that iterates over each unique own enumerable key of all given objects.
* @param objects Objects to pick keys.
* @yields Every unique own key.
*/
export function* keys(...objects: Array<Record<string, any>>): Generator<string, void, unknown> {
const map: Record<string, any> = {};
// Object.create(null) instead Set for more efficiently memory usage on small objects
const seen: Record<string, boolean> = Object.create(null);

// "for" instead "for of" for performance (no extra iterators)
for (let i = 0; i < objects.length; i++) {
const item = objects[i];

// filter spares in objects array
if (!item) continue;

// Object.keys to get only own enumerable string keys and ignore spares
const itemKeys = Object.keys(item);

// "for" instead "for of" for performance (no extra iterators)
for (let j = 0; j < itemKeys.length; j++) {
// non-null assertion because can guarantee that key is string here
const key = itemKeys[j]!;

for (const item of objects) {
for (const key in item) {
if (hasOwn.call(item, key)) {
if (map[key]) {
continue;
} else {
map[key] = true;
yield key;
}
// `key in seen` instead `seen[key]` for micro optimization
if (!(key in seen)) {
seen[key] = true;
yield key;
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/misc/wait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export function wait(ms: number, options: WaitOptions = {}): Promise<void> {
return Promise.reject(signal.reason);
}

return new Promise((resolve, reject) => {
const timerId = setTimeout(() => resolve(), ms);
return new Promise((done, fail) => {
const timerId = setTimeout(() => done(), ms);

if (signal) {
const handleAbort = () => {
clearTimeout(timerId);
reject(signal.reason);
fail(signal.reason);
};

signal.addEventListener('abort', handleAbort, { once: true });
Expand Down