Skip to content

Commit 17e62e7

Browse files
authoredFeb 13, 2024
feat: add CustomPromise (#67)
* feat: add `CustomPromise` * refactor: rename custom promise to promise * refactor(then): move wrappers to the bottom * build(bun): set coverage limit * ci(gh-actions): add codecov v4 * refactor: rewrite the `PromiseState` type * feat: add `then` method * test: replace bun test runner with vitest * build(husky): set test:unit script * feat: add `catch` method * feat: add base promise implementation * build: update lock file * feat: add finally method * refactor: add global variables `resolvedPromise` and `rejectedPromise` * refactor(comparator): simplify structure * refactor: remove unused types * feat: add `all` static method * refactor: replace acc with accumulator * feat: add `race` method * test: add appropriate value for the test * test(race): replace error with error.message * feat: add `allSettled` static method
1 parent 5e1af35 commit 17e62e7

File tree

24 files changed

+882
-360
lines changed

24 files changed

+882
-360
lines changed
 

‎.github/workflows/pull-request.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ jobs:
1717
- name: Unit Testing
1818
run: bun test:unit:coverage
1919

20-
- name: Upload coverage reports to Codecov
21-
uses: codecov/codecov-action@v3
20+
- uses: actions/checkout@master
21+
- uses: codecov/codecov-action@v4
22+
with:
23+
flags: unittests # optional
24+
name: codecov-umbrella
25+
verbose: true
2226
env:
2327
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

‎bun.lockb

-11.4 KB
Binary file not shown.

‎bunfig.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
# always enable coverage
44
coverage = true
5-
coverageThreshold = 0.9
5+
coverageThreshold = 0.6

‎package.json

+9-4
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,35 @@
1717
"devDependencies": {
1818
"@commitlint/cli": "^18.6.0",
1919
"@commitlint/config-conventional": "^18.6.0",
20+
"@types/lodash.delay": "^4.1.9",
2021
"@types/lodash.isequal": "^4.5.8",
22+
"@types/lodash.isfunction": "^3.0.9",
2123
"@typescript-eslint/eslint-plugin": "^6.19.1",
2224
"@typescript-eslint/parser": "^6.19.1",
2325
"@vitest/coverage-v8": "^1.2.2",
2426
"bun-types": "^1.0.25",
2527
"commitizen": "^4.3.0",
26-
"cz-conventional-changelog": "^3.3.0",
2728
"eslint": "^8.56.0",
2829
"eslint-config-airbnb-typescript": "^17.1.0",
2930
"eslint-config-prettier": "^9.1.0",
3031
"eslint-import-resolver-typescript": "^3.6.1",
32+
"eslint-plugin-import": "^2.29.1",
3133
"eslint-plugin-prettier": "^5.1.3",
3234
"git-cz": "^4.9.0",
3335
"husky": "^8.0.3",
34-
"lint-staged": "^15.2.0",
35-
"prettier": "^3.2.4",
36+
"lint-staged": "^15.2.2",
37+
"prettier": "^3.2.5",
3638
"typescript": "^5.3.3",
39+
"utility-types": "^3.11.0",
3740
"vite-tsconfig-paths": "^4.3.1",
3841
"vitest": "^1.2.2"
3942
},
4043
"peerDependencies": {
4144
"typescript": "^5.0.0"
4245
},
4346
"dependencies": {
44-
"lodash.isequal": "^4.5.0"
47+
"lodash.delay": "^4.1.1",
48+
"lodash.isequal": "^4.5.0",
49+
"lodash.isfunction": "^3.0.9"
4550
}
4651
}

‎src/data-structures/doubly-linked-list/index.spec.ts

+2-230
Original file line numberDiff line numberDiff line change
@@ -65,117 +65,6 @@ describe('DoublyLinkedList', () => {
6565
});
6666
});
6767

68-
describe('isEmpty', () => {
69-
it('works correctly', () => {
70-
// Arrange
71-
expect(doublyLinkedList.isEmpty).toBeTruthy();
72-
doublyLinkedList.append(1);
73-
74-
// Act and Assert
75-
expect(doublyLinkedList.isEmpty).toBeFalsy();
76-
});
77-
});
78-
79-
describe('toString', () => {
80-
it('converts a list to string', () => {
81-
doublyLinkedList.append(1).append(2);
82-
83-
expect(doublyLinkedList.toString()).toBe('1,2');
84-
});
85-
it('converts a list to string with custom callback', () => {
86-
// Arrange
87-
type NodeValue = {
88-
key: string;
89-
value: number;
90-
};
91-
92-
const list = new DoublyLinkedList<NodeValue>();
93-
94-
list
95-
.append({
96-
key: 'one',
97-
value: 1,
98-
})
99-
.append({
100-
key: 'two',
101-
value: 2,
102-
});
103-
104-
// Act
105-
const received = list.toString((node) => `${node.value}`);
106-
107-
// Assert
108-
expect(received).toBe('1,2');
109-
});
110-
});
111-
112-
describe('fromArray', () => {
113-
it('creates an empty list when an empty array is passed', () => {
114-
// Act
115-
doublyLinkedList.fromArray([]);
116-
117-
// Assert
118-
expect(doublyLinkedList.head).toBeNull();
119-
expect(doublyLinkedList.tail).toBeNull();
120-
expect(doublyLinkedList.size).toBe(0);
121-
});
122-
123-
it('creates a list with the same nodes as the input array', () => {
124-
// Act
125-
doublyLinkedList.fromArray([1, 2]);
126-
127-
// Assert
128-
expect(doublyLinkedList.toString()).toBe('1,2');
129-
});
130-
});
131-
132-
describe('Iterator', () => {
133-
it('handles an empty list', () => {
134-
// Act
135-
const received = Array.from(doublyLinkedList);
136-
137-
// Assert
138-
expect(received).toEqual([]);
139-
});
140-
it('iterates through the elements of the list', () => {
141-
// Arrange
142-
const expected = [1, 2, 3];
143-
144-
doublyLinkedList.fromArray(expected);
145-
146-
// Act
147-
const received = Array.from(doublyLinkedList, (node) => node.data);
148-
149-
// Assert
150-
expect(received).toEqual(expected);
151-
});
152-
});
153-
154-
describe('toArray', () => {
155-
it('converts an empty list to an array', () => {
156-
// Act
157-
const received = doublyLinkedList.toArray();
158-
159-
// Assert
160-
expect(received).toEqual([]);
161-
expect(doublyLinkedList.head).toBeNull();
162-
expect(doublyLinkedList.tail).toBeNull();
163-
expect(doublyLinkedList.size).toBe(0);
164-
});
165-
166-
it('converts a list to an array', () => {
167-
// Arrange
168-
const expected = [1, 2];
169-
doublyLinkedList.fromArray(expected);
170-
171-
// Act
172-
const received = doublyLinkedList.toArray();
173-
174-
// Assert
175-
expect(received).toEqual(expected);
176-
});
177-
});
178-
17968
describe('prepend', () => {
18069
it('prepends a new node to the beginning of the empty list', () => {
18170
// Arrange
@@ -424,6 +313,7 @@ describe('DoublyLinkedList', () => {
424313
const received = () => doublyLinkedList.insertAt(-1, 1);
425314

426315
// Assert
316+
expect(received).toThrow(RangeError);
427317
expect(received).toThrow(
428318
'Index should be greater than or equal to 0 and less than or equal to the list length.',
429319
);
@@ -434,6 +324,7 @@ describe('DoublyLinkedList', () => {
434324
const received = () => doublyLinkedList.insertAt(10, 1);
435325

436326
// Assert
327+
expect(received).toThrow(RangeError);
437328
expect(received).toThrow(
438329
'Index should be greater than or equal to 0 and less than or equal to the list length.',
439330
);
@@ -592,123 +483,4 @@ describe('DoublyLinkedList', () => {
592483
expect(doublyLinkedList.size).toBe(1);
593484
});
594485
});
595-
596-
describe('indexOf', () => {
597-
it('returns -1 for an empty list', () => {
598-
// Act and Assert
599-
expect(doublyLinkedList.indexOf(42)).toBe(-1);
600-
});
601-
602-
beforeEach(() => {
603-
// Arrange
604-
doublyLinkedList.fromArray([1, 2, 3, 3, 4]);
605-
});
606-
607-
it('returns -1 for a value not present in the list', () => {
608-
// Act and Assert
609-
expect(doublyLinkedList.indexOf(42)).toBe(-1);
610-
});
611-
612-
it('returns the correct index for a value present in the list', () => {
613-
// Act and Assert
614-
expect(doublyLinkedList.indexOf(2)).toBe(1);
615-
});
616-
617-
it('returns the index of the first occurrence of the value', () => {
618-
// Act and Assert
619-
expect(doublyLinkedList.indexOf(3)).toBe(2);
620-
});
621-
622-
it('returns the correct index for the head value', () => {
623-
// Act and Assert
624-
expect(doublyLinkedList.indexOf(1)).toBe(0);
625-
});
626-
627-
it('returns the correct index for the tail value', () => {
628-
// Act and Assert
629-
expect(doublyLinkedList.indexOf(4)).toBe(4);
630-
});
631-
632-
it('handles custom objects and comparison correctly', () => {
633-
type Item = {
634-
key: string;
635-
};
636-
// Arrange
637-
const list = new DoublyLinkedList<Item>().fromArray([
638-
{ key: 'value1' },
639-
{ key: 'value2' },
640-
]);
641-
642-
// Act and Assert
643-
expect(list.indexOf({ key: 'value1' })).toBe(0);
644-
});
645-
});
646-
647-
describe('find', () => {
648-
it('returns null for a not-found node', () => {
649-
// Act and Assert
650-
expect(doublyLinkedList.find(1)).toBeNull();
651-
expect(doublyLinkedList.find((value) => value === 100)).toBeNull();
652-
});
653-
654-
it('finds a node by value', () => {
655-
// Arrange
656-
doublyLinkedList.fromArray([1, 2]);
657-
658-
// Act
659-
const foundedNode = doublyLinkedList.find(2);
660-
661-
// Assert
662-
expect(foundedNode?.data).toBe(2);
663-
});
664-
665-
it('finds a node by predicate', () => {
666-
// Arrange
667-
doublyLinkedList.fromArray([1, 2, 3]);
668-
669-
// Act
670-
const foundedNode = doublyLinkedList.find((value) => value > 2);
671-
672-
// Assert
673-
expect(foundedNode?.data).toBe(3);
674-
});
675-
676-
it('prioritizes predicate over value', () => {
677-
// Arrange
678-
doublyLinkedList.fromArray([1, 2]);
679-
680-
// Act
681-
const foundedNode = doublyLinkedList.find((value) => value > 1);
682-
683-
// Assert
684-
expect(foundedNode?.data).toBe(2);
685-
});
686-
687-
it('returns the first node if multiple nodes match the predicate', () => {
688-
// Arrange
689-
doublyLinkedList.fromArray([1, 2, 3, 4]);
690-
691-
// Act
692-
const foundedNode = doublyLinkedList.find((value) => value > 1);
693-
694-
// Assert
695-
expect(foundedNode?.data).toBe(2);
696-
});
697-
});
698-
699-
describe('clear', () => {
700-
it('removes all nodes from the linked list', () => {
701-
// Arrange
702-
doublyLinkedList.fromArray([1, 2, 3]);
703-
704-
// Act
705-
doublyLinkedList.clear();
706-
707-
// Assert
708-
expect(doublyLinkedList.head).toBeNull();
709-
expect(doublyLinkedList.tail).toBeNull();
710-
expect(doublyLinkedList.size).toBe(0);
711-
expect(doublyLinkedList.isEmpty).toBeTruthy();
712-
});
713-
});
714486
});

‎src/data-structures/doubly-linked-list/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class DoublyLinkedList<T = any> extends BaseLinkedList<T, Node<T>> {
2727
return this;
2828
}
2929

30-
prepend(value: T): this {
30+
prepend(value: T) {
3131
const newNode = new Node(value);
3232

3333
if (this._head === null) {
@@ -112,7 +112,7 @@ export class DoublyLinkedList<T = any> extends BaseLinkedList<T, Node<T>> {
112112
const isInvalidIndex = index < 0 || index > this._size;
113113

114114
if (isInvalidIndex) {
115-
throw new Error(
115+
throw new RangeError(
116116
'Index should be greater than or equal to 0 and less than or equal to the list length.',
117117
);
118118
}

‎src/data-structures/doubly-linked-list/node/index.spec.ts

-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ describe('DoublyLinkedListNode', () => {
77
const doublyList = new DoublyLinkedListNode<number>(1);
88

99
// Assert
10-
expect(doublyList.data).toBe(1);
11-
expect(doublyList.next).toBeNull();
1210
expect(doublyList.prev).toBeNull();
1311
});
1412

@@ -23,8 +21,6 @@ describe('DoublyLinkedListNode', () => {
2321
const list = new DoublyLinkedListNode<typeof expectedValue>(expectedValue);
2422

2523
// Assert
26-
expect(list.data).toEqual(expectedValue);
27-
expect(list.next).toBeNull();
2824
expect(list.prev).toBeNull();
2925
});
3026

‎src/data-structures/hash-map/index.spec.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { beforeEach, describe, expect, it, vi } from 'vitest';
1+
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
22
import { HashMap } from './index';
33

44
describe('HashMap', () => {
@@ -245,46 +245,48 @@ describe('HashMap', () => {
245245
});
246246

247247
describe('forEach', () => {
248+
let callbackSpy: Mock<any, any>;
249+
250+
// Arrange
251+
beforeEach(() => {
252+
callbackSpy = vi.fn();
253+
});
254+
248255
it('iterates over each the key-value pairs', () => {
249256
// Arrange
250257
hashMap.set('one', 1);
251258
hashMap.set('two', 2);
252259
hashMap.set('three', 3);
253260

254-
const callbackFn = vi.fn();
255-
256261
// Act
257-
hashMap.forEach(callbackFn);
262+
hashMap.forEach(callbackSpy);
258263

259264
// Assert
260-
expect(callbackFn).toHaveBeenCalledTimes(3);
265+
expect(callbackSpy).toHaveBeenCalledTimes(3);
261266
});
262267

263268
it('uses the provided thisArg as the context if provided', () => {
264269
// Arrange
265270
hashMap.set('one', 1);
266271

267272
const thisArg = { customContext: true };
268-
const callbackFn = vi.fn();
269273

270274
// Act
271-
hashMap.forEach(callbackFn, thisArg);
275+
hashMap.forEach(callbackSpy, thisArg);
272276

273277
// Assert
274-
expect(callbackFn.mock.calls[0][2]).toBe(thisArg);
278+
expect(callbackSpy.mock.calls[0][2]).toBe(thisArg);
275279
});
276280

277281
it('uses the HashMap as the context if thisArg is not provided', () => {
278282
// Arrange
279283
hashMap.set('one', 1);
280284

281-
const callbackFn = vi.fn();
282-
283285
// Act
284-
hashMap.forEach(callbackFn);
286+
hashMap.forEach(callbackSpy);
285287

286288
// Assert
287-
expect(callbackFn.mock.calls[0][2]).toBe(hashMap);
289+
expect(callbackSpy.mock.calls[0][2]).toBe(hashMap);
288290
});
289291
});
290292

‎src/data-structures/hash-map/index.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class HashMap<K = any, V = any> {
4646
return 'HashMap';
4747
}
4848

49-
#hashCode(key: K): number {
49+
#hashCode(key: K) {
5050
const hashString = String(key);
5151

5252
// The choice of 31 is a common practice in hash functions due to its properties.
@@ -66,7 +66,7 @@ export class HashMap<K = any, V = any> {
6666
return hash;
6767
}
6868

69-
#resizeIfNeeded(): void {
69+
#resizeIfNeeded() {
7070
const RESIZE_THRESHOLD = 0.7;
7171
const loadFactor = this.#size / this.#buckets.length;
7272
if (loadFactor < RESIZE_THRESHOLD) return;
@@ -88,13 +88,13 @@ export class HashMap<K = any, V = any> {
8888
this.#capacity = newCapacity;
8989
}
9090

91-
#findBucketByKey(key: K): LinkedList<KeyValuePair<K, V>> | undefined {
91+
#findBucketByKey(key: K) {
9292
const index = this.#hashCode(key);
9393

94-
return this.#buckets[index];
94+
return this.#buckets[index] as LinkedList<KeyValuePair<K, V>> | undefined;
9595
}
9696

97-
set(key: K, value: V): this {
97+
set(key: K, value: V) {
9898
this.#resizeIfNeeded();
9999

100100
const hash = this.#hashCode(key);
@@ -159,7 +159,7 @@ export class HashMap<K = any, V = any> {
159159
return Boolean(node);
160160
}
161161

162-
delete(key: K): boolean {
162+
delete(key: K) {
163163
const hash = this.#hashCode(key);
164164
const bucket = this.#buckets[hash];
165165

‎src/data-structures/linked-list/index.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ describe('LinkedList', () => {
379379
const received = () => linkedList.insertAt(-1, 1);
380380

381381
// Assert
382+
expect(received).toThrow(RangeError);
382383
expect(received).toThrow(
383384
'Index should be greater than or equal to 0 and less than or equal to the list length.',
384385
);
@@ -389,6 +390,7 @@ describe('LinkedList', () => {
389390
const received = () => linkedList.insertAt(10, 1);
390391

391392
// Assert
393+
expect(received).toThrow(RangeError);
392394
expect(received).toThrow(
393395
'Index should be greater than or equal to 0 and less than or equal to the list length.',
394396
);

‎src/data-structures/linked-list/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { BaseLinkedList, type Predicate } from '@/shared/base-linked-list';
22
import { BaseLinkedListNode as Node } from '@/shared/base-linked-list/node';
33

44
export class LinkedList<T = any> extends BaseLinkedList<T, Node<T>> {
5-
append(value: T): this {
5+
append(value: T) {
66
const newNode = new Node(value);
77

88
if (this._head === null) {
@@ -26,7 +26,7 @@ export class LinkedList<T = any> extends BaseLinkedList<T, Node<T>> {
2626
return this;
2727
}
2828

29-
prepend(value: T): this {
29+
prepend(value: T) {
3030
const newNode = new Node(value);
3131

3232
if (this._head === null) {
@@ -108,7 +108,7 @@ export class LinkedList<T = any> extends BaseLinkedList<T, Node<T>> {
108108
const isInvalidIndex = index < 0 || index > this._size;
109109

110110
if (isInvalidIndex) {
111-
throw new Error(
111+
throw new RangeError(
112112
'Index should be greater than or equal to 0 and less than or equal to the list length.',
113113
);
114114
}

‎src/data-structures/lru-cache/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class LRUCache<Key extends string | number | symbol = any, Value = any> {
3737
return newNode;
3838
}
3939

40-
#deleteByReference(node: Node): void {
40+
#deleteByReference(node: Node) {
4141
if (this.#head === this.#tail) {
4242
this.#head = null;
4343
this.#tail = null;
@@ -65,7 +65,7 @@ export class LRUCache<Key extends string | number | symbol = any, Value = any> {
6565
return node.data.value;
6666
}
6767

68-
put(key: Key, value: Value): void {
68+
put(key: Key, value: Value) {
6969
if (Object.hasOwn(this.#nodeMap, key)) {
7070
const node = this.#nodeMap[key];
7171
this.#deleteByReference(node!);

‎src/data-structures/lru-cache/lru-cache-on-map/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class LRUCacheOnMap<Key = any, Value = any> {
1717
return value;
1818
}
1919

20-
put(key: Key, value: Value): void {
20+
put(key: Key, value: Value) {
2121
if (this.#cache.has(key)) {
2222
this.#cache.delete(key);
2323
}

‎src/data-structures/promise/index.ts

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import isFunction from 'lodash.isfunction';
2+
import { ValuesType } from 'utility-types';
3+
4+
const STATE = {
5+
PENDING: 'pending',
6+
FULFILLED: 'fulfilled',
7+
REJECTED: 'rejected',
8+
} as const;
9+
10+
type State = ValuesType<typeof STATE>;
11+
12+
type Value<T> = T | PromiseLike<T>;
13+
14+
type Callback = () => void;
15+
16+
interface PromiseFulfilledResult<T> {
17+
status: typeof STATE.FULFILLED;
18+
value: T;
19+
}
20+
21+
interface PromiseRejectedResult {
22+
status: typeof STATE.REJECTED;
23+
reason: any;
24+
}
25+
26+
type PromiseSettledResult<T> =
27+
| PromiseFulfilledResult<T>
28+
| PromiseRejectedResult;
29+
30+
export class CustomPromise<T = any> {
31+
#state: State = STATE.PENDING;
32+
33+
#value?: Value<T>;
34+
35+
#onfulfilledCallbacks: Callback[] = [];
36+
37+
#onrejectedCallbacks: Callback[] = [];
38+
39+
static resolve(): CustomPromise<void>;
40+
static resolve<T>(value: T): CustomPromise<Awaited<T>>;
41+
static resolve<T>(value: T | PromiseLike<T>): CustomPromise<Awaited<T>>;
42+
static resolve<T>(value?: T | PromiseLike<T>) {
43+
if (isCustomPromise(value)) {
44+
return value;
45+
}
46+
47+
return new CustomPromise<Awaited<T>>((resolve) =>
48+
resolve(value as Awaited<T>),
49+
);
50+
}
51+
52+
static reject<T = never>(reason?: any) {
53+
if (isCustomPromise(reason)) {
54+
return reason as T;
55+
}
56+
57+
return new CustomPromise<T>((_, reject) => reject(reason));
58+
}
59+
60+
static all<T>(values: Iterable<T | PromiseLike<T>>) {
61+
assertIterable(values);
62+
63+
return new CustomPromise<Awaited<T>[]>((resolve, reject) => {
64+
const promises = Array.from(values);
65+
66+
promises
67+
.reduce<CustomPromise<Awaited<T>[]>>(
68+
(accumulator, promise) =>
69+
accumulator.then((results) =>
70+
CustomPromise.resolve(promise).then((value) =>
71+
results.concat(value),
72+
),
73+
),
74+
CustomPromise.resolve([]),
75+
)
76+
.then(
77+
(results) => {
78+
resolve(results);
79+
},
80+
(error) => {
81+
reject(error);
82+
},
83+
);
84+
});
85+
}
86+
87+
static race<T>(values: Iterable<T | PromiseLike<T>> = []) {
88+
assertIterable(values);
89+
90+
return new CustomPromise<Awaited<T>>((resolve, reject) => {
91+
for (const value of values) {
92+
CustomPromise.resolve(value).then(resolve, reject);
93+
}
94+
});
95+
}
96+
97+
static any<T extends readonly unknown[] | []>(
98+
values: T,
99+
): CustomPromise<Awaited<T[number]>>;
100+
static any<T>(
101+
values: Iterable<T | PromiseLike<T>>,
102+
): CustomPromise<Awaited<T>>;
103+
static any<T>(values: Iterable<T | PromiseLike<T>>) {
104+
assertIterable(values);
105+
106+
const promises = Array.from(values);
107+
let errors: Error[] = [];
108+
109+
return new CustomPromise<Awaited<T>>((resolve, reject) => {
110+
for (const promise of promises) {
111+
CustomPromise.resolve(promise).then(resolve, (error: Error) => {
112+
errors.push(error);
113+
if (errors.length === promises.length) {
114+
reject(new AggregateError('All promises were rejected'));
115+
}
116+
});
117+
}
118+
});
119+
}
120+
121+
static allSettled<T extends readonly unknown[] | []>(
122+
values: T,
123+
): CustomPromise<{
124+
-readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>;
125+
}>;
126+
static allSettled<T>(
127+
values: Iterable<T | PromiseLike<T>>,
128+
): CustomPromise<PromiseSettledResult<Awaited<T>>[]>;
129+
static allSettled<T>(values: Iterable<T | PromiseLike<T>> = []) {
130+
assertIterable(values);
131+
132+
return CustomPromise.all<PromiseSettledResult<Awaited<T>>>(
133+
Array.from(values).map((promise) =>
134+
CustomPromise.resolve(promise).then(
135+
(value) => ({
136+
status: STATE.FULFILLED,
137+
value,
138+
}),
139+
(error) => ({
140+
status: STATE.REJECTED,
141+
reason: error,
142+
}),
143+
),
144+
),
145+
);
146+
}
147+
148+
constructor(
149+
executor: (
150+
resolve: (value: T | PromiseLike<T>) => void,
151+
reject: (reason?: any) => void,
152+
) => void,
153+
) {
154+
try {
155+
executor(this.#resolve.bind(this), this.#reject.bind(this));
156+
} catch (error) {
157+
this.#reject(error);
158+
}
159+
}
160+
161+
#resolve(value: Value<T>) {
162+
if (this.#state === STATE.PENDING) {
163+
this.#state = STATE.FULFILLED;
164+
this.#value = value;
165+
166+
this.#onfulfilledCallbacks.forEach((callback) => {
167+
queueMicrotask(() => callback());
168+
});
169+
170+
this.#onfulfilledCallbacks = [];
171+
}
172+
}
173+
174+
#reject(reason?: any) {
175+
if (this.#state === STATE.PENDING) {
176+
this.#state = STATE.REJECTED;
177+
this.#value = reason;
178+
179+
this.#onrejectedCallbacks.forEach((callback) => {
180+
queueMicrotask(() => callback());
181+
});
182+
183+
this.#onrejectedCallbacks = [];
184+
}
185+
}
186+
187+
then<TResult1 = T, TResult2 = never>(
188+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
189+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
190+
) {
191+
return new CustomPromise<TResult1 | TResult2>((resolve, reject) => {
192+
type Handler = typeof onfulfilled | typeof onrejected;
193+
194+
const executeHandler = (handler: Handler) => {
195+
try {
196+
if (isFunction(handler)) {
197+
const result = handler(this.#value as T);
198+
199+
if (isCustomPromise(result)) {
200+
result.then(resolve, reject);
201+
} else {
202+
resolve(result);
203+
}
204+
} else {
205+
reject(this.#value);
206+
}
207+
} catch (error) {
208+
reject(error);
209+
}
210+
};
211+
212+
const handlers = {
213+
[STATE.PENDING]: () => {
214+
this.#onfulfilledCallbacks.push(() => executeHandler(onfulfilled));
215+
this.#onrejectedCallbacks.push(() => executeHandler(onrejected));
216+
},
217+
[STATE.FULFILLED]: () => executeHandler(onfulfilled),
218+
[STATE.REJECTED]: () => executeHandler(onrejected),
219+
};
220+
221+
handlers[this.#state]();
222+
});
223+
}
224+
225+
catch<TResult = never>(
226+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
227+
) {
228+
return this.then(null, onrejected);
229+
}
230+
231+
finally(onfinally?: (() => void) | undefined | null) {
232+
return this.then(
233+
(value) => {
234+
if (isFunction(onfinally)) {
235+
onfinally();
236+
}
237+
238+
return value;
239+
},
240+
(reason) => {
241+
if (isFunction(onfinally)) {
242+
onfinally();
243+
}
244+
245+
return reason;
246+
},
247+
);
248+
}
249+
}
250+
251+
function isCustomPromise(value: any): value is CustomPromise {
252+
return value instanceof CustomPromise;
253+
}
254+
255+
const assertIterable = (value: any) => {
256+
if (!value[Symbol.iterator]) {
257+
throw new TypeError(
258+
`${typeof value} is not iterable (cannot read property Symbol(Symbol.iterator))`,
259+
);
260+
}
261+
};

‎src/data-structures/promise/promise.spec.ts

+504
Large diffs are not rendered by default.

‎src/data-structures/tree/binary-tree-node.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class BinaryTreeNode<T = any> {
3333
return this.leftHeight - this.rightHeight;
3434
}
3535

36-
setValue(value: T): this {
36+
setValue(value: T) {
3737
this.value = value;
3838

3939
return this;

‎src/shared/base-linked-list/index.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import isFunction from 'lodash.isfunction';
12
import { Comparator, CompareFunction } from '../comparator';
23
import { BaseLinkedListNode, type Callback } from './node';
34

@@ -114,7 +115,3 @@ export abstract class BaseLinkedList<
114115
abstract deleteHead(): Node | null;
115116
abstract deleteTail(): Node | null;
116117
}
117-
118-
function isFunction(a: unknown): a is Predicate {
119-
return typeof a === 'function';
120-
}

‎src/shared/base-linked-list/node/index.spec.ts

+13-35
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,33 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { beforeEach, describe, expect, it } from 'vitest';
22
import { BaseLinkedListNode } from './index';
33

4-
class BaseLinkedListNodeTest<T> extends BaseLinkedListNode<T> {}
5-
64
describe('BaseLinkedListNode', () => {
7-
it('creates list node with value', () => {
8-
// Act
9-
const node = new BaseLinkedListNodeTest<number>(1);
5+
let node: BaseLinkedListNode<number>;
106

11-
// Assert
12-
expect(node.data).toEqual(1);
13-
expect(node.next).toBeNull();
7+
beforeEach(() => {
8+
node = new BaseLinkedListNode<number>(1);
149
});
1510

16-
it('creates list node with object as a value', () => {
17-
// Arrange
18-
const nodeValueMock = {
19-
value: 1,
20-
key: 'test',
21-
};
22-
23-
// Act
24-
const node = new BaseLinkedListNodeTest<typeof nodeValueMock>(
25-
nodeValueMock,
26-
);
27-
11+
it('creates initial state correctly', () => {
2812
// Assert
29-
expect(node.data.value).toEqual(1);
30-
expect(node.data.key).toEqual('test');
13+
expect(node).toBeDefined();
14+
expect(node.data).toEqual(1);
3115
expect(node.next).toBeNull();
3216
});
3317

3418
it('links nodes together', () => {
35-
// Arrange
36-
const node2 = new BaseLinkedListNodeTest<number>(1);
37-
3819
// Act
39-
const node1 = new BaseLinkedListNodeTest<number>(2, node2);
20+
const node0 = new BaseLinkedListNode<number>(0, node);
4021

4122
// Assert
42-
expect(node1.next).toBeDefined();
43-
expect(node2.next).toBeNull();
44-
expect(node1.data).toEqual(2);
45-
expect(node1.next?.data).toEqual(1);
23+
expect(node0.next).toBeDefined();
24+
expect(node.next).toBeNull();
25+
expect(node0.data).toEqual(0);
26+
expect(node0.next?.data).toEqual(1);
4627
});
4728

4829
describe('toString', () => {
4930
it('converts node to string', () => {
50-
// Arrange
51-
const node = new BaseLinkedListNodeTest<number>(1);
52-
5331
// Act and Assert
5432
expect(node.toString()).toEqual('1');
5533
});
@@ -61,7 +39,7 @@ describe('BaseLinkedListNode', () => {
6139
key: 'test',
6240
};
6341

64-
const list = new BaseLinkedListNodeTest<typeof nodeValue>(nodeValue);
42+
const list = new BaseLinkedListNode<typeof nodeValue>(nodeValue);
6543
const toStringCallback = (x: typeof nodeValue) =>
6644
`value: ${x.value}, key: ${x.key}`;
6745

‎src/shared/base-linked-list/node/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class BaseLinkedListNode<T = any> {
1010
this.next = next;
1111
}
1212

13-
toString(callback?: Callback<T>): string {
13+
toString(callback?: Callback<T>) {
1414
return callback ? callback(this.data) : `${this.data}`;
1515
}
1616
}

‎src/shared/comparator/comparator.ts

-45
This file was deleted.

‎src/shared/comparator/comparator.spec.ts ‎src/shared/comparator/index.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, test as it } from 'vitest';
2-
import { Comparator } from './comparator';
3-
import type { CompareFunction } from './comparator';
2+
import { Comparator } from '.';
3+
import type { CompareFunction } from '.';
44

55
describe('Comparator', () => {
66
it('compares values using default comparison function', () => {

‎src/shared/comparator/index.ts

+45-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,45 @@
1-
export * from './comparator';
1+
import isEqual from 'lodash.isequal';
2+
3+
export type CompareFunction<T> = (a: T, b: T) => -1 | 0 | 1;
4+
5+
export class Comparator<T = unknown> {
6+
#compare: CompareFunction<T>;
7+
8+
#isEqual = isEqual;
9+
10+
static defaultCompareFunction<T = unknown>(a: T, b: T) {
11+
if (a === b) return 0;
12+
13+
return a < b ? -1 : 1;
14+
}
15+
16+
constructor(compareFunction?: CompareFunction<T>) {
17+
this.#compare = compareFunction ?? Comparator.defaultCompareFunction;
18+
}
19+
20+
equal(a: unknown, b: unknown) {
21+
return this.#isEqual(a, b);
22+
}
23+
24+
lessThan(a: T, b: T) {
25+
return this.#compare(a, b) < 0;
26+
}
27+
28+
greaterThan(a: T, b: T) {
29+
return this.#compare(a, b) > 0;
30+
}
31+
32+
lessThanOrEqual(a: T, b: T) {
33+
return this.lessThan(a, b) || this.equal(a, b);
34+
}
35+
36+
greaterThanOrEqual(a: T, b: T) {
37+
return this.greaterThan(a, b) || this.equal(a, b);
38+
}
39+
40+
reverse() {
41+
const compareOriginal = this.#compare;
42+
43+
this.#compare = (a, b) => compareOriginal(b, a);
44+
}
45+
}

‎tsconfig.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"forceConsistentCasingInFileNames": true,
1818
"allowJs": true,
1919
"types": [
20-
"bun-types", // add Bun global
21-
],
22-
},
20+
"bun-types" // add Bun global
21+
]
22+
}
2323
}

‎vite.config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { defineConfig } from 'vitest/config';
1+
/// <reference types="vitest" />
2+
23
import tsconfigPaths from 'vite-tsconfig-paths';
4+
import { defineConfig } from 'vitest/config';
35

46
export default defineConfig({
57
plugins: [tsconfigPaths()],

0 commit comments

Comments
 (0)
Please sign in to comment.