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

Add pMapSkip #39

Merged
merged 9 commits into from Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
34 changes: 33 additions & 1 deletion index.d.ts
Expand Up @@ -58,4 +58,36 @@ export default function pMap<Element, NewElement>(
input: Iterable<Element>,
mapper: Mapper<Element, NewElement>,
options?: Options
): Promise<NewElement[]>;
): Promise<Array<Exclude<NewElement, typeof pMapSkip>>>;

/**
Return this value from a `mapper` function to skip adding a value in the returned array.

@example
```
import pMap, {pMapSkip} from 'p-map';
import got from 'got';

const sites = [
getWebsiteFromUsername('sindresorhus'), //=> Promise
'https://avajs.dev',
'https://example.invalid',
'https://github.com'
];

const mapper = async site => {
try {
const {requestUrl} = await got.head(site);
return requestUrl;
} catch {
return pMapSkip
}
};

const result = await pMap(sites, mapper, {concurrency: 2});

console.log(result);
//=> ['https://sindresorhus.com/', 'https://avajs.dev/', 'https://github.com/']
Richienb marked this conversation as resolved.
Show resolved Hide resolved
```
*/
export const pMapSkip: unique symbol;
15 changes: 14 additions & 1 deletion index.js
Expand Up @@ -19,6 +19,7 @@ export default async function pMap(

const result = [];
const errors = [];
const skippedIndexes = [];
const iterator = iterable[Symbol.iterator]();
let isRejected = false;
let isIterableDone = false;
Expand All @@ -41,6 +42,10 @@ export default async function pMap(
if (!stopOnError && errors.length > 0) {
reject(new AggregateError(errors));
} else {
for (const skippedIndex of skippedIndexes) {
result.splice(skippedIndex, 1);
}

resolve(result);
}
}
Expand All @@ -53,7 +58,13 @@ export default async function pMap(
(async () => {
try {
const element = await nextItem.value;
result[index] = await mapper(element, index);
const value = await mapper(element, index);
if (value === pMapSkip) {
skippedIndexes.push(index);
} else {
result[index] = value;
}

resolvingCount--;
next();
} catch (error) {
Expand All @@ -78,3 +89,5 @@ export default async function pMap(
}
});
}

export const pMapSkip = Symbol('skip');
10 changes: 9 additions & 1 deletion index.test-d.ts
@@ -1,5 +1,5 @@
import {expectType, expectAssignable} from 'tsd';
import pMap, {Options, Mapper} from './index.js';
import pMap, {Options, Mapper, pMapSkip} from './index.js';

const sites = [
'https://sindresorhus.com',
Expand Down Expand Up @@ -40,3 +40,11 @@ expectType<Promise<string[]>>(pMap(sites, (site: string) => site));
expectType<Promise<number[]>>(pMap(sites, (site: string) => site.length));

expectType<Promise<number[]>>(pMap(numbers, (number: number) => number * 2));

expectType<Promise<number[]>>(pMap(numbers, (number: number) => {
if (number % 2 === 0) {
return number * 2;
}

return pMapSkip;
}));
30 changes: 30 additions & 0 deletions readme.md
Expand Up @@ -72,6 +72,36 @@ Default: `true`

When set to `false`, instead of stopping when a promise rejects, it will wait for all the promises to settle and then reject with an [aggregated error](https://github.com/sindresorhus/aggregate-error) containing all the errors from the rejected promises.

### pMapSkip

Return this value from a `mapper` function to skip adding a value in the returned array.
Richienb marked this conversation as resolved.
Show resolved Hide resolved

```js
import pMap, {pMapSkip} from 'p-map';
import got from 'got';

const sites = [
getWebsiteFromUsername('sindresorhus'), //=> Promise
'https://avajs.dev',
'https://example.invalid',
'https://github.com'
];

const mapper = async site => {
try {
const {requestUrl} = await got.head(site);
return requestUrl;
} catch {
return pMapSkip
}
};

const result = await pMap(sites, mapper, {concurrency: 2});

console.log(result);
//=> ['https://sindresorhus.com/', 'https://avajs.dev/', 'https://github.com/']
```

## p-map for enterprise

Available as part of the Tidelift Subscription.
Expand Down
10 changes: 9 additions & 1 deletion test.js
Expand Up @@ -4,7 +4,7 @@ import inRange from 'in-range';
import timeSpan from 'time-span';
import randomInt from 'random-int';
import AggregateError from 'aggregate-error';
import pMap from './index.js';
import pMap, {pMapSkip} from './index.js';

const sharedInput = [
Promise.resolve([10, 300]),
Expand Down Expand Up @@ -107,3 +107,11 @@ test('aggregate errors when stopOnError is false', async t => {
await t.throwsAsync(pMap(errorInput1, mapper, {concurrency: 1, stopOnError: false}), {instanceOf: AggregateError, message: /foo(.|\n)*bar/});
await t.throwsAsync(pMap(errorInput2, mapper, {concurrency: 1, stopOnError: false}), {instanceOf: AggregateError, message: /bar(.|\n)*foo/});
});

test('.skip', async t => {
t.deepEqual(await pMap([
1,
pMapSkip,
2
], async value => value), [1, 2]);
});