Skip to content

Commit

Permalink
utils: Add asyncMap and asyncForEach + 100% coverage for them
Browse files Browse the repository at this point in the history
  • Loading branch information
Atinux committed Dec 6, 2017
1 parent a3c3040 commit 30c31ec
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 8 deletions.
26 changes: 18 additions & 8 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,8 @@ exports.waitFor = function (ms) {
*/
exports.waitForEvent = function (emmiter, eventName, timeout = -1) {
return new Promise((resolve, reject) => {
let resolveCalled = false
const r = (...args) => {
if (resolveCalled) return
resolve(...args)
resolveCalled = true
}
emmiter.once(eventName, (...args) => r([...args]))
if (timeout >= 0) setTimeout(() => reject(new Error('Timeout')), timeout)
emmiter.once(eventName, (...args) => resolve([...args]))
if (timeout >= 0) setTimeout(() => reject(new Error(`Wait for event timeout (${timeout}ms)`)), timeout)
})
}

Expand All @@ -59,3 +53,19 @@ exports.asyncObject = async function (obj = {}) {
})
return container
}

/*
** asyncMap(array, fn): Promise<Array>
*/
exports.asyncMap = async function (array, fn) {
return await Promise.all(array.map(fn))
}

/*
** asyncForEach(array, fn): Promise<void>
*/
exports.asyncForEach = async function (array, fn) {
for (let i = 0; i < array.length; i++) {
await fn(array[i], i, array)
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
151 changes: 151 additions & 0 deletions test/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const test = require('ava')
const EventEmitter = require('events').EventEmitter

const { asyncObject, waitFor, cb, ok, waitForEvent, asyncMap, asyncForEach } = require('../lib/utils')

async function p(val, delay = 0) {
await waitFor(delay)
return val
}

async function fail(delay = 0) {
await waitFor(delay)
throw new Error('fail')
}

/*
** asyncObject
*/
test('Returns the results of promises + non-promises', async (t) => {
const res = await asyncObject({
foo: p('foo'),
bar: p('bar'),
baz: p('baz'),
n: null,
u: undefined
})
const expected = { foo: 'foo', bar: 'bar', baz: 'baz', n: null, u: undefined }
t.deepEqual(res, expected)
})

test('Returns empty object with no parameter', async (t) => {
const obj = await asyncObject()
t.deepEqual(obj, {})
})

test('Throws an error is one promise throws', async (t) => {
const err = await t.throws(asyncObject({
foo: fail(100),
bar: p('bar'),
fail: fail()
}))
t.is(err.message, 'fail')
})

/*
** cb
*/
function read(file, callback) {
if (file === 'foo.txt') {
return setTimeout(() => callback(null, 'contents'), 0)
}
setTimeout(() => callback(new Error('file not found')), 0)
}

test('Passes args and receives results', async (t) => {
const result = await cb(read, 'foo.txt')
t.is(result, 'contents')
})

test('Throws an error if (err) is truthy', async (t) => {
const err = await t.throws(cb(read, 'bar.txt'))
t.is(err.message, 'file not found')
})

/*
** ok
*/
test('Returns a value on success', async (t) => {
const foo = await ok(p('foo'))
t.is(foo, 'foo')
})

test('Returns undefined on error', async (t) => {
const res = await ok(fail())
t.is(res, undefined)
})

/*
** waitForEvent
*/
function newEmitter(eventName, ms, ...args) {
const emitter = new EventEmitter()
setTimeout(() => {
emitter.emit(eventName, ...args)
}, ms)

return emitter
}

test('Should wait until event is emitted', async (t) => {
const emitter = newEmitter('listen', 100)
const start = Date.now()
await waitForEvent(emitter, 'listen')
t.true(Date.now() - start >= 90)
})

test('Should work without parameter', async (t) => {
const emitter = newEmitter('close', 10, 'foo', 123)
const res = await waitForEvent(emitter, 'close')
t.deepEqual(res, ['foo', 123])
})

test('Should trigger timeout', async (t) => {
const emitter = new EventEmitter()
const error = await t.throws(waitForEvent(emitter, 'close', 100))
t.true(error.message.includes('Wait for event timeout (100ms)'))
})

/*
** waitFor
*/
test('Should wait for 100ms', async (t) => {
const start = Date.now()
await waitFor(100)
t.true(Date.now() - start >= 100)
})

test('Should work without parameter', async (t) => {
const start = Date.now()
await waitFor()
t.true(Date.now() - start <= 30)
})

/*
** asyncMap
*/
test('Returns the results of promises + non-promises', async (t) => {
const res = await asyncMap(['foo', 'bar', 'baz', null, undefined], (doc) => p(doc))
const expected = ['foo', 'bar', 'baz', null, undefined]
t.deepEqual(res, expected)
})

/*
** asyncForEach
*/
test('Returns the results of promises + non-promises', async (t) => {
const array = ['foo', 'bar', 'baz', null, undefined]
const res = {}
await asyncForEach(array, async (value) => {
await waitFor(10)
res[String(value)] = value
})
const expected = {
'foo': 'foo',
'bar': 'bar',
'baz': 'baz',
'null': null,
'undefined': undefined
}
t.deepEqual(res, expected)
})

0 comments on commit 30c31ec

Please sign in to comment.