Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add allSettled mode for Collector class #233

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased][unreleased]

## [5.0.1][] - 2023-12-15

- Add new properties `isRejectable` to Collector class: change behavior of `wait`, `take`, `collect` method in case of errors

## [5.0.0][] - 2023-12-10

- Changed `Semaphore` signature, moved all parameters to `options`
Expand Down
39 changes: 32 additions & 7 deletions lib/collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,35 @@ class Collector {
count = 0;
exact = true;
timeout = 0;
isRejectable = true;
#fulfill = null;
#reject = null;
#timer = null;
#cause = null;

constructor(keys, { exact = true, timeout = 0 } = {}) {
constructor(keys, { exact = true, timeout = 0, isRejectable = true } = {}) {
this.keys = keys;
this.isRejectable = isRejectable;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check if isRejectable is boolean

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

if (exact === false) this.exact = false;
if (typeof timeout === 'number') this.#timeout(timeout);
}

#setData(key, data) {
if (!this.isRejectable) {
this.set(key, getSettled(null, data));
return;
}
this.set(key, data);
}

#setError(key, err) {
if (!this.isRejectable) {
this.set(key, getSettled(err));
return;
}
this.fail(err);
}

#timeout(msec) {
this.timeout = msec;
if (this.#timer) {
Expand Down Expand Up @@ -48,23 +66,23 @@ class Collector {

take(key, fn, ...args) {
fn(...args, (err, data) => {
if (err) this.fail(err);
else this.set(key, data);
if (err) this.#setError(key, err);
else this.#setData(key, data);
});
}

wait(key, fn, ...args) {
fn(...args).then(
(data) => this.set(key, data),
(err) => this.fail(err),
(data) => this.#setData(key, data),
(err) => this.#setError(key, err),
);
}

collect(sources) {
for (const [key, collector] of Object.entries(sources)) {
collector.then(
(data) => this.set(key, data),
(err) => this.fail(err),
(data) => this.#setData(key, data),
(err) => this.#setError(key, err),
);
}
}
Expand All @@ -88,6 +106,13 @@ class Collector {
}
}

function getSettled(err = null, value = null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be a separate function, it should be inside a class Collector. And what must do this function? Function set takes the argument value not as an object.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made getSettled as static method in class Collector before, but @tshemsedinov said said that the function is better.
Function should return an object like Promise.allSettled() depending on whether the result is "fulfilled" or "rejected".

if (err) {
return { status: 'rejected', reason: err };
}
return { status: 'fulfilled', value };
}

const collect = (keys, options) => new Collector(keys, options);

module.exports = { Collector, collect };
1 change: 1 addition & 0 deletions metautil.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export function sizeToBytes(size: string): number;
export interface CollectorOptions {
exact?: boolean;
timeout?: number;
isRejectable?: boolean;
}

type AsyncFunction = (...args: Array<unknown>) => Promise<unknown>;
Expand Down
97 changes: 97 additions & 0 deletions test/collector.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,100 @@ metatests.test('Collector: error in then chain', (test) => {
},
);
});

metatests.test(
'Collector: wait isRejectable = true, all fulfilled',
async (test) => {
const expectedResult = { key1: 'User: Marcus', key2: 'User: Caesar' };
const dc = collect(['key1', 'key2']);

const fn = async (name) =>
new Promise((resolve) => {
setTimeout(() => resolve(`User: ${name}`), 100);
});
dc.wait('key1', fn, 'Marcus');
dc.wait('key2', fn, 'Caesar');

const result = await dc;
test.strictSame(result, expectedResult);
test.end();
},
);

metatests.test(
'Collector: wait isRejectable = true, rejected',
async (test) => {
const expectedResult = 'Error: Caesar';
const dc = collect(['key1', 'key2']);

const fn = async (name) =>
new Promise((resolve) => {
setTimeout(() => resolve(`User: ${name}`), 100);
});
const rejectFn = async (name) =>
new Promise((_, reject) => {
setTimeout(() => reject(`Error: ${name}`), 100);
});

dc.wait('key1', fn, 'Marcus');
dc.wait('key2', rejectFn, 'Caesar');

try {
await dc;
test.fail('Not rejected');
} catch (error) {
test.strictSame(error, expectedResult);
}
test.end();
},
);

metatests.test(
'Collector: wait isRejectable = false, all fulfilled',
async (test) => {
const expectedResult = {
key1: { status: 'fulfilled', value: 'User: Marcus' },
key2: { status: 'fulfilled', value: 'User: Caesar' },
};
const dc = collect(['key1', 'key2'], { isRejectable: false });

const fn = async (name) =>
new Promise((resolve) => {
setTimeout(() => resolve(`User: ${name}`), 100);
});

dc.wait('key1', fn, 'Marcus');
dc.wait('key2', fn, 'Caesar');

const result = await dc;
test.strictSame(result, expectedResult);
test.end();
},
);

metatests.test(
'Collector: wait isRejectable = false rejected',
async (test) => {
const expectedResult = {
key1: { status: 'fulfilled', value: 'User: Marcus' },
key2: { status: 'rejected', reason: 'Error: Caesar' },
};
const dc = collect(['key1', 'key2'], { isRejectable: false });

const fn = async (name) =>
new Promise((resolve) => {
setTimeout(() => resolve(`User: ${name}`), 100);
});
const rejectFn = async (name) =>
new Promise((_, reject) => {
setTimeout(() => reject(`Error: ${name}`), 100);
});

dc.wait('key1', fn, 'Marcus');
dc.wait('key2', rejectFn, 'Caesar');

const result = await dc;
test.strictSame(result, expectedResult);
test.end();
},
);