Skip to content

Commit

Permalink
Require Node.js 12.20 and move to ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Aug 25, 2021
1 parent 728565e commit 05f1972
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 75 deletions.
6 changes: 2 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ jobs:
fail-fast: false
matrix:
node-version:
- 14
- 12
- 10
- 16
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
46 changes: 21 additions & 25 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
declare namespace pLocate {
interface Options {
/**
Number of concurrently pending promises returned by `tester`. Minimum: `1`.
export interface Options {
/**
The number of concurrently pending promises returned by `tester`.
@default Infinity
*/
readonly concurrency?: number;
Minimum: `1`
/**
Preserve `input` order when searching.
@default Infinity
*/
readonly concurrency?: number;

Disable this to improve performance if you don't care about the order.
/**
Preserve `input` order when searching.
@default true
*/
readonly preserveOrder?: boolean;
}
Disable this to improve performance if you don't care about the order.
@default true
*/
readonly preserveOrder?: boolean;
}

/**
Expand All @@ -27,27 +27,23 @@ Get the first fulfilled promise that satisfies the provided testing function.
@example
```
import pathExists = require('path-exists');
import pLocate = require('p-locate');
import {pathExists} from 'path-exists';
import pLocate from 'p-locate';
const files = [
'unicorn.png',
'rainbow.png', // Only this one actually exists on disk
'pony.png'
];
(async () => {
const foundPath = await pLocate(files, file => pathExists(file));
const foundPath = await pLocate(files, file => pathExists(file));
console.log(foundPath);
//=> 'rainbow'
})();
console.log(foundPath);
//=> 'rainbow'
```
*/
declare function pLocate<ValueType>(
export default function pLocate<ValueType>(
input: Iterable<PromiseLike<ValueType> | ValueType>,
tester: (element: ValueType) => PromiseLike<boolean> | boolean,
options?: pLocate.Options
options?: Options
): Promise<ValueType | undefined>;

export = pLocate;
36 changes: 17 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use strict';
const pLimit = require('p-limit');
import pLimit from 'p-limit';

class EndError extends Error {
constructor(value) {
Expand All @@ -8,10 +7,10 @@ class EndError extends Error {
}
}

// The input can also be a promise, so we await it
// The input can also be a promise, so we await it.
const testElement = async (element, tester) => tester(await element);

// The input can also be a promise, so we `Promise.all()` them both
// The input can also be a promise, so we `Promise.all()` them both.
const finder = async element => {
const values = await Promise.all(element);
if (values[1] === true) {
Expand All @@ -21,20 +20,21 @@ const finder = async element => {
return false;
};

const pLocate = async (iterable, tester, options) => {
options = {
concurrency: Infinity,
preserveOrder: true,
...options
};

const limit = pLimit(options.concurrency);

// Start all the promises concurrently with optional limit
export default async function pLocate(
iterable,
tester,
{
concurrency = Number.POSITIVE_INFINITY,
preserveOrder = true,
} = {},
) {
const limit = pLimit(concurrency);

// Start all the promises concurrently with optional limit.
const items = [...iterable].map(element => [element, limit(testElement, element, tester)]);

// Check the promises either serially or concurrently
const checkLimit = pLimit(options.preserveOrder ? 1 : Infinity);
// Check the promises either serially or concurrently.
const checkLimit = pLimit(preserveOrder ? 1 : Number.POSITIVE_INFINITY);

try {
await Promise.all(items.map(element => checkLimit(finder, element)));
Expand All @@ -45,6 +45,4 @@ const pLocate = async (iterable, tester, options) => {

throw error;
}
};

module.exports = pLocate;
}
5 changes: 3 additions & 2 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import {expectType} from 'tsd';
import pLocate = require('.');
import pLocate from './index.js';

const files = new Set([
'unicorn.png',
'rainbow.png',
Promise.resolve('pony.png')
Promise.resolve('pony.png'),
]);

pLocate(files, file => {
Expand Down
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=10"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"test": "xo && ava && tsd"
Expand Down Expand Up @@ -41,14 +43,14 @@
"bluebird"
],
"dependencies": {
"p-limit": "^3.0.2"
"p-limit": "^4.0.0"
},
"devDependencies": {
"ava": "^2.4.0",
"delay": "^4.1.0",
"in-range": "^2.0.0",
"time-span": "^4.0.0",
"tsd": "^0.13.1",
"xo": "^0.32.1"
"ava": "^3.15.0",
"delay": "^5.0.0",
"in-range": "^3.0.0",
"time-span": "^5.0.0",
"tsd": "^0.17.0",
"xo": "^0.44.0"
}
}
14 changes: 6 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,19 @@ $ npm install p-locate
Here we find the first file that exists on disk, in array order.

```js
const pathExists = require('path-exists');
const pLocate = require('p-locate');
import {pathExists} from 'path-exists';
import pLocate from 'p-locate';

const files = [
'unicorn.png',
'rainbow.png', // Only this one actually exists on disk
'pony.png'
];

(async () => {
const foundPath = await pLocate(files, file => pathExists(file));
const foundPath = await pLocate(files, file => pathExists(file));

console.log(foundPath);
//=> 'rainbow'
})();
console.log(foundPath);
//=> 'rainbow'
```

*The above is just an example. Use [`locate-path`](https://github.com/sindresorhus/locate-path) if you need this.*
Expand Down Expand Up @@ -62,7 +60,7 @@ Type: `number`\
Default: `Infinity`\
Minimum: `1`

Number of concurrently pending promises returned by `tester`.
The number of concurrently pending promises returned by `tester`.

##### preserveOrder

Expand Down
20 changes: 11 additions & 9 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
import {serial as test} from 'ava';
import test from 'ava';
import delay from 'delay';
import inRange from 'in-range';
import timeSpan from 'time-span';
import pLocate from '.';
import pLocate from './index.js';

const input = [
[1, 300],
[2, 400],
[3, 200],
Promise.resolve([4, 100]) // Ensures promises work in the input
Promise.resolve([4, 100]), // Ensures promises work in the input
];

const tester = async ([value, ms]) => {
await delay(ms);
return value === 2 || value === 3;
};

test('main', async t => {
test.serial('main', async t => {
const end = timeSpan();
t.is((await pLocate(input, tester))[0], 2);
t.true(inRange(end(), {start: 370, end: 450}), 'should be time of item `2`');
});

test('option {preserveOrder:false}', async t => {
test.serial('option {preserveOrder:false}', async t => {
const end = timeSpan();
t.is((await pLocate(input, tester, {preserveOrder: false}))[0], 3);
t.true(inRange(end(), {start: 170, end: 250}), 'should be time of item `3`');
});

test('option {concurrency:1}', async t => {
test.serial('option {concurrency:1}', async t => {
const end = timeSpan();
t.is((await pLocate(input, tester, {concurrency: 1}))[0], 2);
t.true(inRange(end(), {start: 670, end: 750}), 'should be time of items `1` and `2`, since they run serially');
});

test('returns `undefined` when nothing could be found', async t => {
test.serial('returns `undefined` when nothing could be found', async t => {
t.is((await pLocate([1, 2, 3], () => false)), undefined);
});

test('rejected return value in `tester` rejects the promise', async t => {
test.serial('rejected return value in `tester` rejects the promise', async t => {
const fixtureError = new Error('fixture');

await t.throwsAsync(
pLocate([1, 2, 3], () => Promise.reject(fixtureError)),
fixtureError.message
{
message: fixtureError.message,
},
);
});

0 comments on commit 05f1972

Please sign in to comment.