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 .allSettled() method #24

Merged
merged 6 commits into from
Aug 1, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ jobs:
fail-fast: false
matrix:
node-version:
- 16
- 14
- 12
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
78 changes: 78 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
type Awaited<ValueType> = ValueType extends undefined ? ValueType : ValueType extends PromiseLike<infer ResolveValueType> ? ResolveValueType : ValueType;

// /~https://github.com/microsoft/TypeScript/blob/582e404a1041ce95d22939b73f0b4d95be77c6ec/lib/lib.es2020.promise.d.ts#L21-L31
export type PromiseSettledResult<ResolveValueType> = {
status: 'fulfilled';
value: ResolveValueType;
} | {
status: 'rejected';
reason: any;
};

export interface Options {
/**
Number of concurrently pending promises. Minimum: `1`.
Expand Down Expand Up @@ -133,6 +142,75 @@ export class PProgress<ValueType> extends Promise<ValueType> {
options?: Options
): PProgress<Iterable<ReturnValue>>;

/**
Like [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) but also exposes the total progress of all of the promises like `PProgress.all`.

@param promises - Array of promises or promise-returning functions, similar to [p-all](/~https://github.com/sindresorhus/p-all).

@example
```
import pProgress, {PProgress} from 'p-progress';
import delay from 'delay';

const progressPromise = () => pProgress(async progress => {
progress(0.14);
await delay(52);
progress(0.37);
await delay(104);
progress(0.41);
await delay(26);
progress(0.93);
await delay(55);
return 1;
});

const progressPromise2 = () => pProgress(async progress => {
progress(0.14);
await delay(52);
progress(0.37);
await delay(104);
progress(0.41);
await delay(26);
progress(0.93);
await delay(55);
throw new Error('Catch me if you can!');
});

const allProgressPromise = PProgress.allSettled([
progressPromise(),
progressPromise2()
]);

allProgressPromise.onProgress(console.log);
//=> 0.0925
//=> 0.3425
//=> 0.5925
//=> 0.6025
//=> 0.7325
//=> 0.9825
//=> 1

console.log(await allProgressPromise);
//=> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: Error: Catch me if you can!}]
```
*/
static allSettled<Promises extends Array<PromiseFactory<unknown> | PromiseLike<unknown>>>(
promises: readonly [...Promises],
options?: Options
): PProgress<{
[Promise_ in keyof Promises]: PromiseSettledResult<Promises[Promise_] extends PromiseLike<unknown>
? Awaited<Promises[Promise_]>
: (
Promises[Promise_] extends PromiseFactory<unknown>
? Awaited<ReturnType<Promises[Promise_]>>
: Promises[Promise_]
)>
}>;
static allSettled<ReturnValue>(
promises: Iterable<PromiseFactory<ReturnValue> | PromiseLike<ReturnValue>>,
options?: Options
): PProgress<Iterable<PromiseSettledResult<ReturnValue>>>;

/**
Accepts a function that gets `instance.progress` as an argument and is called for every progress event.
*/
Expand Down
67 changes: 58 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,55 @@ export class PProgress extends Promise {
});
}

static allSettled(promises, {concurrency} = {}) {
if (
typeof concurrency === 'number' &&
!(promises.every(promise => typeof promise === 'function'))
) {
throw new TypeError('When `options.concurrency` is set, the first argument must be an Array of Promise-returning functions');
}

return pProgress(progress => {
const progressMap = new Map();

const reportProgress = () => {
progress(sum(progressMap) / promises.length);
};

const mapper = async index => {
const nextValue = promises[index];
const promise = typeof nextValue === 'function' ? nextValue() : nextValue;
progressMap.set(promise, 0);

if (promise instanceof PProgress) {
promise.onProgress(percentage => {
progressMap.set(promise, percentage);
reportProgress();
});
}

try {
return {
status: 'fulfilled',
value: await promise
};
} catch (error) {
return {
status: 'rejected',
reason: error
};
} finally {
progressMap.set(promise, 1);
reportProgress();
}
};

return pTimes(promises.length, mapper, {
concurrency
});
});
}

constructor(executor) {
const setProgress = progress => {
if (progress > 1 || progress < 0) {
Expand Down Expand Up @@ -116,12 +165,12 @@ export class PProgress extends Promise {
}
}

const pProgress = input => new PProgress(async (resolve, reject, progress) => {
try {
resolve(await input(progress));
} catch (error) {
reject(error);
}
});

export default pProgress;
export default function pProgress(input) {
return new PProgress(async (resolve, reject, progress) => {
try {
resolve(await input(progress));
} catch (error) {
reject(error);
}
});
}
10 changes: 9 additions & 1 deletion index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expectType} from 'tsd';
import pProgress, {PProgress, ProgressNotifier} from './index.js';
import pProgress, {PProgress, ProgressNotifier, PromiseSettledResult} from './index.js';

const progressPromise = new PProgress(async (resolve, reject, progress) => {
expectType<(progress: number) => void>(progress);
Expand Down Expand Up @@ -496,3 +496,11 @@ expectType<PProgress<Iterable<string | number | boolean | symbol | string[]>>>(
{concurrency: 1}
)
);
expectType<
PProgress<[PromiseSettledResult<string>, PromiseSettledResult<number>]>
>(
PProgress.allSettled([
Promise.resolve('sindresorhus.com'),
Promise.resolve(1)
])
);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12.2"
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd"
Expand Down Expand Up @@ -44,6 +44,7 @@
"in-range": "^3.0.0",
"time-span": "^5.0.0",
"tsd": "^0.16.0",
"typescript": "^4.3.5",
"xo": "^0.40.1"
},
"xo": {
Expand Down
26 changes: 21 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ await progressPromise;

Convenience method to run multiple promises and get a total progress of all of them. It counts normal promises with progress `0` when pending and progress `1` when resolved. For `PProgress` type promises, it listens to their `onProgress()` method for more fine grained progress reporting. You can mix and match normal promises and `PProgress` promises.

### PProgress.allSettled(promises, options?)

Like [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) but also exposes the total progress of all of the promises like `PProgress.all`.

```js
import pProgress, {PProgress} from 'p-progress';
import delay from 'delay';
Expand All @@ -117,13 +121,24 @@ const progressPromise = () => pProgress(async progress => {
await delay(26);
progress(0.93);
await delay(55);
return 1;
});

const progressPromise2 = () => pProgress(async progress => {
progress(0.14);
await delay(52);
progress(0.37);
await delay(104);
progress(0.41);
await delay(26);
progress(0.93);
await delay(55);
throw new Error('Catch me if you can!');
});

const allProgressPromise = PProgress.all([
delay(103),
const allProgressPromise = PProgress.allSettled([
progressPromise(),
delay(55),
delay(209)
progressPromise2()
]);

allProgressPromise.onProgress(console.log);
Expand All @@ -135,7 +150,8 @@ allProgressPromise.onProgress(console.log);
//=> 0.9825
//=> 1

await allProgressPromise;
console.log(await allProgressPromise);
//=> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: Error: Catch me if you can!}]
```

#### promises
Expand Down
Loading