diff --git a/.travis.yml b/.travis.yml index e155464..f98fed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,3 @@ node_js: - '12' - '10' - '8' - - '6' diff --git a/index.d.ts b/index.d.ts index 90269fc..8f79629 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,74 +5,61 @@ declare namespace pMap { @default Infinity */ - concurrency?: number; + readonly concurrency?: number; /** 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. @default true */ - stopOnError?: boolean; + readonly stopOnError?: boolean; } /** Function which is called for every item in `input`. Expected to return a `Promise` or value. - @param input - Iterated element. + @param element - Iterated element. @param index - Index of the element in the source array. */ type Mapper = ( - input: Element, + element: Element, index: number ) => NewElement | Promise; } -declare const pMap: { - /** - Returns a `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled, or rejects if any of the promises reject. The fulfilled value is an `Array` of the fulfilled values returned from `mapper` in `input` order. - - @param input - Iterated over concurrently in the `mapper` function. - @param mapper - Function which is called for every item in `input`. Expected to return a `Promise` or value. +/** +@param input - Iterated over concurrently in the `mapper` function. +@param mapper - Function which is called for every item in `input`. Expected to return a `Promise` or value. +@returns A `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled, or rejects if any of the promises reject. The fulfilled value is an `Array` of the fulfilled values returned from `mapper` in `input` order. - @example - ``` - import pMap = require('p-map'); - import got = require('got'); +@example +``` +import pMap = require('p-map'); +import got = require('got'); - const sites = [ - getWebsiteFromUsername('sindresorhus'), //=> Promise - 'ava.li', - 'todomvc.com', - 'github.com' - ]; +const sites = [ + getWebsiteFromUsername('https://sindresorhus'), //=> Promise + 'https://ava.li', + 'https://github.com' +]; - (async () => { - const mapper = async site => { - const {requestUrl} = await got.head(site); - return requestUrl; - }; +(async () => { + const mapper = async site => { + const {requestUrl} = await got.head(site); + return requestUrl; + }; - const result = await pMap(sites, mapper, {concurrency: 2}); - - console.log(result); - //=> ['http://sindresorhus.com/', 'http://ava.li/', 'http://todomvc.com/', 'http://github.com/'] - })(); - ``` - */ - ( - input: Iterable, - mapper: pMap.Mapper, - options?: pMap.Options - ): Promise; + const result = await pMap(sites, mapper, {concurrency: 2}); - // TODO: Remove this for the next major release, refactor the whole definition to: - // declare function pMap( - // input: Iterable, - // mapper: pMap.Mapper, - // options?: pMap.Options - // ): Promise; - // export = pMap; - default: typeof pMap; -}; + console.log(result); + //=> ['https://sindresorhus.com/', 'https://ava.li/', 'https://github.com/'] +})(); +``` +*/ +declare function pMap( + input: Iterable, + mapper: pMap.Mapper, + options?: pMap.Options +): Promise; export = pMap; diff --git a/index.js b/index.js index 250c5e7..5a8aeb2 100644 --- a/index.js +++ b/index.js @@ -1,64 +1,63 @@ 'use strict'; const AggregateError = require('aggregate-error'); -const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { - options = Object.assign({ - concurrency: Infinity, - stopOnError: true - }, options); - - if (typeof mapper !== 'function') { - throw new TypeError('Mapper function is required'); - } - - const {concurrency, stopOnError} = options; +module.exports = async ( + iterable, + mapper, + { + concurrency = Infinity, + stopOnError = true + } = {} +) => { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } - if (!(typeof concurrency === 'number' && concurrency >= 1)) { - throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); - } + if (!(typeof concurrency === 'number' && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); + } - const ret = []; - const errors = []; - const iterator = iterable[Symbol.iterator](); - let isRejected = false; - let isIterableDone = false; - let resolvingCount = 0; - let currentIndex = 0; + const ret = []; + const errors = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; - const next = () => { - if (isRejected) { - return; - } + const next = () => { + if (isRejected) { + return; + } - const nextItem = iterator.next(); - const i = currentIndex; - currentIndex++; + const nextItem = iterator.next(); + const i = currentIndex; + currentIndex++; - if (nextItem.done) { - isIterableDone = true; + if (nextItem.done) { + isIterableDone = true; - if (resolvingCount === 0) { - if (!stopOnError && errors.length !== 0) { - reject(new AggregateError(errors)); - } else { - resolve(ret); + if (resolvingCount === 0) { + if (!stopOnError && errors.length !== 0) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } } - } - return; - } + return; + } - resolvingCount++; + resolvingCount++; - Promise.resolve(nextItem.value) - .then(element => mapper(element, i)) - .then( - value => { - ret[i] = value; + (async () => { + try { + const element = await nextItem.value; + ret[i] = await mapper(element, i); resolvingCount--; next(); - }, - error => { + } catch (error) { if (stopOnError) { isRejected = true; reject(error); @@ -68,18 +67,15 @@ const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { next(); } } - ); - }; + })(); + }; - for (let i = 0; i < concurrency; i++) { - next(); + for (let i = 0; i < concurrency; i++) { + next(); - if (isIterableDone) { - break; + if (isIterableDone) { + break; + } } - } -}); - -module.exports = pMap; -// TODO: Remove this for the next major release -module.exports.default = pMap; + }); +}; diff --git a/index.test-d.ts b/index.test-d.ts index c08d0ce..a160384 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -2,9 +2,17 @@ import {expectType} from 'tsd'; import pMap = require('.'); import {Options, Mapper} from '.'; -const sites = ['ava.li', 'todomvc.com', 'github.com']; - -const numbers = [0, 1, 2]; +const sites = [ + 'https://sindresorhus.com', + 'https://ava.li', + 'https://github.com' +]; + +const numbers = [ + 0, + 1, + 2 +]; const asyncMapper = async (site: string) => site; const asyncSyncMapper = (site: string, index: number) => diff --git a/package.json b/package.json index 7b0d470..ad7f3d4 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "url": "sindresorhus.com" }, "engines": { - "node": ">=6" + "node": ">=8" }, "scripts": { "test": "xo && ava && tsd" @@ -38,13 +38,13 @@ "bluebird" ], "dependencies": { - "aggregate-error": "^2.0.0" + "aggregate-error": "^3.0.0" }, "devDependencies": { - "ava": "^1.4.1", + "ava": "^2.2.0", "delay": "^4.1.0", - "in-range": "^1.0.0", - "random-int": "^1.0.0", + "in-range": "^2.0.0", + "random-int": "^2.0.0", "time-span": "^3.1.0", "tsd": "^0.7.2", "xo": "^0.24.0" diff --git a/readme.md b/readme.md index 87e7838..4941cb7 100644 --- a/readme.md +++ b/readme.md @@ -19,10 +19,9 @@ const pMap = require('p-map'); const got = require('got'); const sites = [ - getWebsiteFromUsername('sindresorhus'), //=> Promise - 'ava.li', - 'todomvc.com', - 'github.com' + getWebsiteFromUsername('https://sindresorhus'), //=> Promise + 'https://ava.li', + 'https://github.com' ]; (async () => { @@ -34,7 +33,7 @@ const sites = [ const result = await pMap(sites, mapper, {concurrency: 2}); console.log(result); - //=> ['http://sindresorhus.com/', 'http://ava.li/', 'http://todomvc.com/', 'http://github.com/'] + //=> ['https://sindresorhus.com/', 'https://ava.li/', 'https://github.com/'] })(); ``` diff --git a/test.js b/test.js index 227b023..3a606af 100644 --- a/test.js +++ b/test.js @@ -31,13 +31,13 @@ const mapper = ([value, ms]) => delay(ms, {value}); test('main', async t => { const end = timeSpan(); t.deepEqual(await pMap(input, mapper), [10, 20, 30]); - t.true(inRange(end(), 290, 430)); + t.true(inRange(end(), {start: 290, end: 430})); }); test('concurrency: 1', async t => { const end = timeSpan(); t.deepEqual(await pMap(input, mapper, {concurrency: 1}), [10, 20, 30]); - t.true(inRange(end(), 590, 760)); + t.true(inRange(end(), {start: 590, end: 760})); }); test('concurrency: 4', async t => { @@ -79,7 +79,6 @@ test('async with concurrency: 2 (out of order time sequence)', async t => { test('enforce number in options.concurrency', async t => { await t.throwsAsync(pMap([], () => {}, {concurrency: 0}), TypeError); - await t.throwsAsync(pMap([], () => {}, {concurrency: undefined}), TypeError); await t.notThrowsAsync(pMap([], () => {}, {concurrency: 1})); await t.notThrowsAsync(pMap([], () => {}, {concurrency: 10})); await t.notThrowsAsync(pMap([], () => {}, {concurrency: Infinity}));