Skip to content

Commit

Permalink
Require Node.js 8, add TypeScript definition, refactor to async-await (
Browse files Browse the repository at this point in the history
  • Loading branch information
BendingBender authored and sindresorhus committed Mar 12, 2019
1 parent 00ba4d3 commit f6071ee
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 39 deletions.
3 changes: 1 addition & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
* text=auto
*.js text eol=lf
* text=auto eol=lf
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ language: node_js
node_js:
- '10'
- '8'
- '6'
35 changes: 35 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {Options} from 'p-map';

export type Mapper<ValueType, KeyType, MappedValueType> = (
element: PromiseLike<ValueType> | ValueType,
key: KeyType
) => MappedValueType | PromiseLike<MappedValueType>;

/**
* Like [`Promise.all()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) but for `Map` and `Object`.
*
* @param input - Resolves entry values that are promises. Other values are passed through.
* @param mapper - Receives the current value and key as parameters. Expected to return a `Promise` or value.
* @param options - See the [`p-map` options](https://github.com/sindresorhus/p-map#options).
* @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 the same as `input`, but with a fulfilled version of each entry value, or the fulfilled value returned from `mapper`, if defined.
*/
export default function pProps<
KeyType extends unknown,
ValueType extends unknown,
MappedValueType = ValueType
>(
input: Map<KeyType, PromiseLike<ValueType> | ValueType>,
mapper?: Mapper<ValueType, KeyType, MappedValueType>,
options?: Options
): Promise<Map<KeyType, MappedValueType>>;
export default function pProps<
KeyType extends string,
ValueType extends unknown,
MappedValueType = ValueType
>(
input: {[key in KeyType]: PromiseLike<ValueType> | ValueType},
mapper?: Mapper<ValueType, KeyType, MappedValueType>,
options?: Options
): Promise<{[key in KeyType]: MappedValueType}>;

export {Options} from 'p-map';
41 changes: 22 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
'use strict';

const pMap = require('p-map');

const map = (input, mapper, options) => {
return pMap(input.entries(), ([key, value]) => mapper(value, key), options).then(values => {
const ret = new Map();
const map = async (input, mapper, options) => {
const values = await pMap(input.entries(), ([key, value]) => mapper(value, key), options);
const result = new Map();

for (const [i, key] of [...input.keys()].entries()) {
ret.set(key, values[i]);
}
for (const [i, key] of [...input.keys()].entries()) {
result.set(key, values[i]);
}

return ret;
});
return result;
};

const obj = (input, mapper, options) => {
// TODO: Use `Object.entries()` when targeting Node.js 8
return pMap(Object.keys(input), key => mapper(input[key], key), options).then(values => {
const ret = {};
const obj = async (input, mapper, options) => {
const values = await pMap(Object.entries(input), ([key, value]) => mapper(value, key), options);
const result = {};

for (const [i, key] of Object.keys(input).entries()) {
ret[key] = values[i];
}
for (const [i, key] of Object.keys(input).entries()) {
result[key] = values[i];
}

return ret;
});
return result;
};

module.exports = (input, mapper, options) => {
const pProps = (input, mapper, options) => {
mapper = mapper || (value => value);
return input instanceof Map ? map(input, mapper, options) : obj(input, mapper, options);
return input instanceof Map ?
map(input, mapper, options) :
obj(input, mapper, options);
};

module.exports = pProps;
module.exports.default = pProps;
84 changes: 84 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {expectType} from 'tsd-check';
import pProps from '.';

expectType<Promise<{[key in 'foo']: string}>>(pProps({foo: 'bar'}));
expectType<Promise<{[key in 'foo']: boolean}>>(
pProps({foo: 'bar'}, (value, key) => {
expectType<string | PromiseLike<string>>(value);
expectType<'foo'>(key);
return Math.random() > 0.5 ? false : Promise.resolve(true);
})
);
expectType<Promise<{[key in 'foo']: boolean}>>(
pProps(
{foo: 'bar'},
(value, key) => {
expectType<string | PromiseLike<string>>(value);
expectType<'foo'>(key);
return Math.random() > 0.5 ? false : Promise.resolve(true);
},
{
concurrency: 1
}
)
);

const hashMap = {
unicorn: Promise.resolve(1),
foo: 'bar'
};

expectType<Promise<{[key: string]: string | number}>>(
pProps<string, string | number>(hashMap)
);
expectType<Promise<{[key: string]: boolean}>>(
pProps<string, string | number, boolean>(hashMap, (value, key) => {
expectType<string | number | PromiseLike<string | number>>(value);
expectType<string>(key);
return Math.random() > 0.5 ? false : Promise.resolve(true);
})
);
expectType<Promise<{[key: string]: boolean}>>(
pProps<string, string | number, boolean>(
hashMap,
(value, key) => {
expectType<string | number | PromiseLike<string | number>>(value);
expectType<string>(key);
return Math.random() > 0.5 ? false : Promise.resolve(true);
},
{
concurrency: 1
}
)
);

const map = new Map<number, string | Promise<string>>([
[1, Promise.resolve('1')],
[2, '2']
]);

pProps(map).then(result => {
expectType<string | undefined>(result.get(1));
});

expectType<Promise<Map<number, string>>>(pProps(map));
expectType<Promise<Map<number, boolean>>>(
pProps(map, (value, key) => {
expectType<string | PromiseLike<string>>(value);
expectType<number>(key);
return Math.random() > 0.5 ? false : Promise.resolve(true);
})
);
expectType<Promise<Map<number, boolean>>>(
pProps(
map,
(value, key) => {
expectType<string | PromiseLike<string>>(value);
expectType<number>(key);
return Math.random() > 0.5 ? false : Promise.resolve(true);
},
{
concurrency: 1
}
)
);
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
"url": "sindresorhus.com"
},
"engines": {
"node": ">=6"
"node": ">=8"
},
"scripts": {
"test": "xo && ava"
"test": "xo && ava && tsd-check"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"promise",
Expand All @@ -36,11 +37,12 @@
"bluebird"
],
"dependencies": {
"p-map": "^1.2.0"
"p-map": "^2.0.0"
},
"devDependencies": {
"ava": "^0.25.0",
"delay": "^2.0.0",
"xo": "^0.23.0"
"ava": "^1.3.1",
"delay": "^4.1.0",
"tsd-check": "^0.3.0",
"xo": "^0.24.0"
}
}
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Resolves entry values that are promises. Other values are passed through.

Type: `Function`

Expected to return a `Promise` or value.
Receives the current value and key as parameters. Expected to return a `Promise` or value.

#### options

Expand Down
18 changes: 9 additions & 9 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import test from 'ava';
import delay from 'delay';
import m from '.';
import pProps from '.';

test('main', async t => {
t.deepEqual(
await m({
await pProps({
foo: delay(100).then(() => 1),
bar: Promise.resolve(2),
faz: 3
Expand All @@ -19,7 +19,7 @@ test('main', async t => {

test('`Map` input', async t => {
t.deepEqual(
await m(new global.Map([
await pProps(new global.Map([
['foo', Promise.resolve(1)],
['bar', 2]
])),
Expand All @@ -31,8 +31,8 @@ test('`Map` input', async t => {
});

test('rejects if any of the input promises reject', async t => {
await t.throws(
m({
await t.throwsAsync(
pProps({
foo: Promise.resolve(1),
bar: Promise.reject(new Error('bar'))
}),
Expand All @@ -41,13 +41,13 @@ test('rejects if any of the input promises reject', async t => {
});

test('handles empty object', async t => {
t.deepEqual(await m({}), {});
t.deepEqual((await m(new global.Map([]))), new global.Map([]));
t.deepEqual(await pProps({}), {});
t.deepEqual((await pProps(new global.Map([]))), new global.Map([]));
});

test('with mapper', async t => {
t.deepEqual(
await m({
await pProps({
foo: 1,
baz: Promise.resolve(2)
}, (value, key) => Promise.resolve(value).then(resolvedValue => key + resolvedValue)),
Expand All @@ -60,7 +60,7 @@ test('with mapper', async t => {

test('`Map` input with mapper', async t => {
t.deepEqual(
await m(new global.Map([
await pProps(new global.Map([
['foo', 1],
['bar', Promise.resolve(2)]
]), (value, key) => Promise.resolve(value).then(resolvedValue => key + resolvedValue)),
Expand Down

0 comments on commit f6071ee

Please sign in to comment.