Skip to content

Commit

Permalink
Add pMapSkip (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
Richienb committed Jun 28, 2021
1 parent 4b5f9e7 commit c9c0882
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 4 deletions.
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 including the 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/']
```
*/
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 @@ -58,7 +63,13 @@ export default async function pMap(
return;
}

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 @@ -83,3 +94,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 including the value in the returned array.

```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 @@ -108,6 +108,14 @@ test('aggregate errors when stopOnError is false', async t => {
await t.throwsAsync(pMap(errorInput2, mapper, {concurrency: 1, stopOnError: false}), {instanceOf: AggregateError, message: /bar(.|\n)*foo/});
});

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

test('do not run mapping after stop-on-error happened', async t => {
const input = [1, delay(300, {value: 2}), 3];
const mappedValues = [];
Expand Down

0 comments on commit c9c0882

Please sign in to comment.