parallel-park 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/run-jobs.js +42 -17
- package/package.json +1 -1
- package/src/run-jobs.ts +49 -21
package/README.md
CHANGED
|
@@ -10,7 +10,9 @@ Parallel/concurrent async work, optionally using multiple processes
|
|
|
10
10
|
|
|
11
11
|
`runJobs` is kinda like `Promise.all`, but instead of running everything at once, it'll only run a few Promises at a time (you can choose how many to run at once). It's inspired by [Bluebird's Promise.map function](http://bluebirdjs.com/docs/api/promise.map.html).
|
|
12
12
|
|
|
13
|
-
To use it, you pass in an array of inputs and a mapper function that transforms each input into a Promise. You can also optionally specify the maximum number of Promises to wait on at a time by passing an object with a `concurrency` property, which is a number. The concurrency defaults to 8.
|
|
13
|
+
To use it, you pass in an iterable (array, set, generator function, etc) of inputs and a mapper function that transforms each input into a Promise. You can also optionally specify the maximum number of Promises to wait on at a time by passing an object with a `concurrency` property, which is a number. The concurrency defaults to 8.
|
|
14
|
+
|
|
15
|
+
When using an iterable, if the iterable yields a Promise (ie. `iterable.next() returns { done: false, value: Promise }`), then the yielded Promise will be awaited before being passed into your mapper function. Additionally, async iterables are supported; if `iterable.next()` returns a Promise, it will be awaited.
|
|
14
16
|
|
|
15
17
|
```ts
|
|
16
18
|
import { runJobs } from "parallel-park";
|
package/dist/run-jobs.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runJobs = void 0;
|
|
4
|
+
function isThenable(value) {
|
|
5
|
+
return (typeof value === "object" &&
|
|
6
|
+
value != null &&
|
|
7
|
+
// @ts-ignore accessing .then
|
|
8
|
+
typeof value.then === "function");
|
|
9
|
+
}
|
|
10
|
+
const NOTHING = Symbol("NOTHING");
|
|
4
11
|
async function runJobs(inputs, mapper, {
|
|
5
12
|
/**
|
|
6
13
|
* How many jobs are allowed to run at once.
|
|
@@ -9,22 +16,42 @@ concurrency = 8, } = {}) {
|
|
|
9
16
|
if (concurrency < 1) {
|
|
10
17
|
throw new Error("Concurrency can't be less than one; that doesn't make any sense.");
|
|
11
18
|
}
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
const inputsArray = [];
|
|
20
|
+
const inputIteratorFactory = inputs[Symbol.asyncIterator || NOTHING] || inputs[Symbol.iterator];
|
|
21
|
+
const inputIterator = inputIteratorFactory.call(inputs);
|
|
22
|
+
const maybeLength = Array.isArray(inputs) ? inputs.length : null;
|
|
23
|
+
let iteratorDone = false;
|
|
24
|
+
async function readInput() {
|
|
25
|
+
let nextResult = inputIterator.next();
|
|
26
|
+
if (isThenable(nextResult)) {
|
|
27
|
+
nextResult = await nextResult;
|
|
28
|
+
}
|
|
29
|
+
if (nextResult.done) {
|
|
30
|
+
iteratorDone = true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
let value = nextResult.value;
|
|
35
|
+
if (isThenable(value)) {
|
|
36
|
+
value = await value;
|
|
37
|
+
}
|
|
38
|
+
inputsArray.push(value);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
14
41
|
}
|
|
15
|
-
concurrency = Math.min(concurrency, inputs.length);
|
|
16
42
|
let unstartedIndex = 0;
|
|
17
|
-
const results = new Array(
|
|
43
|
+
const results = new Array(maybeLength || 0);
|
|
18
44
|
const runningPromises = new Set();
|
|
19
45
|
let error = null;
|
|
20
|
-
function takeInput() {
|
|
46
|
+
async function takeInput() {
|
|
47
|
+
const read = await readInput();
|
|
48
|
+
if (!read)
|
|
49
|
+
return;
|
|
21
50
|
const inputIndex = unstartedIndex;
|
|
22
51
|
unstartedIndex++;
|
|
23
|
-
const input =
|
|
24
|
-
const promise = mapper(input, inputIndex,
|
|
25
|
-
if (
|
|
26
|
-
promise == null ||
|
|
27
|
-
typeof promise.then !== "function") {
|
|
52
|
+
const input = inputsArray[inputIndex];
|
|
53
|
+
const promise = mapper(input, inputIndex, maybeLength || Infinity);
|
|
54
|
+
if (!isThenable(promise)) {
|
|
28
55
|
throw new Error("Mapper function passed into runJobs didn't return a Promise. The mapper function should always return a Promise. The easiest way to ensure this is the case is to make your mapper function an async function.");
|
|
29
56
|
}
|
|
30
57
|
const promiseWithMore = promise.then((result) => {
|
|
@@ -36,20 +63,18 @@ concurrency = 8, } = {}) {
|
|
|
36
63
|
});
|
|
37
64
|
runningPromises.add(promiseWithMore);
|
|
38
65
|
}
|
|
39
|
-
function proceed() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
takeInput();
|
|
43
|
-
}
|
|
66
|
+
async function proceed() {
|
|
67
|
+
while (!iteratorDone && runningPromises.size < concurrency) {
|
|
68
|
+
await takeInput();
|
|
44
69
|
}
|
|
45
70
|
}
|
|
46
|
-
proceed();
|
|
71
|
+
await proceed();
|
|
47
72
|
while (runningPromises.size > 0 && !error) {
|
|
48
73
|
await Promise.race(runningPromises.values());
|
|
49
74
|
if (error) {
|
|
50
75
|
throw error;
|
|
51
76
|
}
|
|
52
|
-
proceed();
|
|
77
|
+
await proceed();
|
|
53
78
|
}
|
|
54
79
|
if (error) {
|
|
55
80
|
throw error;
|
package/package.json
CHANGED
package/src/run-jobs.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
function isThenable<T>(value: unknown): value is Promise<T> {
|
|
2
|
+
return (
|
|
3
|
+
typeof value === "object" &&
|
|
4
|
+
value != null &&
|
|
5
|
+
// @ts-ignore accessing .then
|
|
6
|
+
typeof value.then === "function"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const NOTHING = Symbol("NOTHING");
|
|
11
|
+
|
|
1
12
|
export async function runJobs<T, U>(
|
|
2
|
-
inputs:
|
|
13
|
+
inputs: Iterable<T | Promise<T>> | AsyncIterable<T | Promise<T>>,
|
|
3
14
|
mapper: (input: T, index: number, length: number) => Promise<U>,
|
|
4
15
|
{
|
|
5
16
|
/**
|
|
@@ -19,30 +30,49 @@ export async function runJobs<T, U>(
|
|
|
19
30
|
);
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
const inputsArray: Array<T> = [];
|
|
34
|
+
const inputIteratorFactory =
|
|
35
|
+
inputs[Symbol.asyncIterator || NOTHING] || inputs[Symbol.iterator];
|
|
36
|
+
const inputIterator = inputIteratorFactory.call(inputs);
|
|
37
|
+
const maybeLength = Array.isArray(inputs) ? inputs.length : null;
|
|
25
38
|
|
|
26
|
-
|
|
39
|
+
let iteratorDone = false;
|
|
40
|
+
|
|
41
|
+
async function readInput(): Promise<boolean> {
|
|
42
|
+
let nextResult = inputIterator.next();
|
|
43
|
+
if (isThenable(nextResult)) {
|
|
44
|
+
nextResult = await nextResult;
|
|
45
|
+
}
|
|
46
|
+
if (nextResult.done) {
|
|
47
|
+
iteratorDone = true;
|
|
48
|
+
return false;
|
|
49
|
+
} else {
|
|
50
|
+
let value = nextResult.value;
|
|
51
|
+
if (isThenable<T>(value)) {
|
|
52
|
+
value = await value;
|
|
53
|
+
}
|
|
54
|
+
inputsArray.push(value);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
27
58
|
|
|
28
59
|
let unstartedIndex = 0;
|
|
29
60
|
|
|
30
|
-
const results = new Array(
|
|
61
|
+
const results = new Array(maybeLength || 0);
|
|
31
62
|
const runningPromises = new Set();
|
|
32
63
|
let error: Error | null = null;
|
|
33
64
|
|
|
34
|
-
function takeInput() {
|
|
65
|
+
async function takeInput() {
|
|
66
|
+
const read = await readInput();
|
|
67
|
+
if (!read) return;
|
|
68
|
+
|
|
35
69
|
const inputIndex = unstartedIndex;
|
|
36
70
|
unstartedIndex++;
|
|
37
71
|
|
|
38
|
-
const input =
|
|
39
|
-
const promise = mapper(input, inputIndex,
|
|
72
|
+
const input = inputsArray[inputIndex];
|
|
73
|
+
const promise = mapper(input, inputIndex, maybeLength || Infinity);
|
|
40
74
|
|
|
41
|
-
if (
|
|
42
|
-
typeof promise !== "object" ||
|
|
43
|
-
promise == null ||
|
|
44
|
-
typeof promise.then !== "function"
|
|
45
|
-
) {
|
|
75
|
+
if (!isThenable(promise)) {
|
|
46
76
|
throw new Error(
|
|
47
77
|
"Mapper function passed into runJobs didn't return a Promise. The mapper function should always return a Promise. The easiest way to ensure this is the case is to make your mapper function an async function."
|
|
48
78
|
);
|
|
@@ -61,21 +91,19 @@ export async function runJobs<T, U>(
|
|
|
61
91
|
runningPromises.add(promiseWithMore);
|
|
62
92
|
}
|
|
63
93
|
|
|
64
|
-
function proceed() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
takeInput();
|
|
68
|
-
}
|
|
94
|
+
async function proceed() {
|
|
95
|
+
while (!iteratorDone && runningPromises.size < concurrency) {
|
|
96
|
+
await takeInput();
|
|
69
97
|
}
|
|
70
98
|
}
|
|
71
99
|
|
|
72
|
-
proceed();
|
|
100
|
+
await proceed();
|
|
73
101
|
while (runningPromises.size > 0 && !error) {
|
|
74
102
|
await Promise.race(runningPromises.values());
|
|
75
103
|
if (error) {
|
|
76
104
|
throw error;
|
|
77
105
|
}
|
|
78
|
-
proceed();
|
|
106
|
+
await proceed();
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
if (error) {
|