From ecc01b686ecf8da2de7f74a3fb173ede1df1dd91 Mon Sep 17 00:00:00 2001 From: David Braun Date: Fri, 13 Oct 2017 21:32:27 -0400 Subject: [PATCH] reduce: Throw TypeError if the channel contains no values and `initialValue` is not provided. --- README.md | 15 +++++++ lib/index.js | 10 ++++- test/index.js | 106 +++++++++++++++++++++++++------------------------- 3 files changed, 77 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 67d2dc3..b760142 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,21 @@ value has been shifted out or placed in the buffer. reserved value used to indicate a closed channel. #### reduce(callbackfn[, initialValue]) + +`callbackfn` should be a function that takes two arguments (unlike `Array`'s +version which takes four). `reduce` calls the callback, as a function, once for +each value after the first value present in the channel. + +`callbackfn` is called with two arguments: the `previousValue` (value from the +previous call to `callbackfn`) and the `currentValue`. The first time that +callback is called, the `previousValue` and `currentValue` can be one of two +values. If an `initialValue` was provided in the call to `reduce`, then +`previousValue` will be equal to `initialValue` and `currentValue` will be equal +to the first value in the channel. If no `initialValue` was provided, then +`previousValue` will be equal to the first value in the channel and +`currentValue` will be equal to the second. It is a `TypeError` if the channel +contains no values and `initialValue` is not provided. + #### shift() -> async Returns a promise that resolves when an value is received from the channel. diff --git a/lib/index.js b/lib/index.js index 53ee611..0cd785a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -182,14 +182,20 @@ const Channel = function (bufferLength = 0) { await readOnly.forEach((currentValue) => { if (previousValueDefined) { - previousValue = callbackfn(previousValue, currentValue, 0, readOnly) + previousValue = callbackfn(previousValue, currentValue) } else { previousValue = currentValue previousValueDefined = true } }) - return previousValue + if (previousValueDefined) { + return previousValue + } else { + throw new TypeError( + `No values in channel and initialValue wasn't provided.` + ) + } }, shift: function () { diff --git a/test/index.js b/test/index.js index 7856555..2c82d8e 100644 --- a/test/index.js +++ b/test/index.js @@ -4,6 +4,20 @@ const assert = require('@nodeguy/assert') const Channel = require('../lib') const stream = require('stream') +const assertRejects = async (callback, reason) => { + try { + await callback() + } catch (exception) { + if (reason) { + assert.deepEqual(exception, reason) + } + + return + } + + assert.fail(null, reason, `Missing expected rejection.`) +} + const toArray = async (channel) => { const array = [] @@ -147,35 +161,22 @@ describe(`Channel`, function () { describe(`Channel object`, function () { describe(`close`, function () { - it(`can't close an already closed channel`, function (done) { + it(`can't close an already closed channel`, function () { const channel = Channel() channel.close() - channel.close() - .then(() => { - done(new Error()) - }) - .catch((reason) => { - assert.deepEqual( - reason, - new Error(`Can't close an already-closed channel.`) - ) - - done() - }) + return assertRejects(async () => { + await channel.close() + }, new Error(`Can't close an already-closed channel.`)) }) it(`can't push to a closed channel`, async function () { const channel = Channel() channel.close() - return (async () => { + return assertRejects(async () => { await channel.push(0) - })().then(() => { - assert(false) - }).catch((reason) => { - assert.deepEqual(reason, new Error(`Can't push to closed channel.`)) - }) + }, new Error(`Can't push to closed channel.`)) }) it(`returns 'undefined' immediately from shift`, async function () { @@ -239,43 +240,31 @@ describe(`Channel object`, function () { it(`outside select`, function () { const channel = Channel() - return (async () => { - await channel.push(undefined) - })().then(() => { - assert(false) - }).catch((reason) => { - assert.deepEqual(reason, new TypeError( - `Can't push 'undefined' to channel, use close instead.` - )) - }) + return assertRejects( + async () => { + await channel.push(undefined) + }, + new TypeError(`Can't push 'undefined' to channel, use close instead.`) + ) }) it(`inside select`, function () { const channel = Channel() - return (async () => { + return assertRejects(async () => { await Channel.select(channel.push(undefined)) - })().then(() => { - assert(false) - }).catch((reason) => { - assert.deepEqual(reason, new TypeError( - `Can't push 'undefined' to channel, use close instead.` - )) - }) + }, new TypeError( + `Can't push 'undefined' to channel, use close instead.` + )) }) }) it(`disallows multiple values`, function () { const channel = Channel() - return (async () => { + return assertRejects(async () => { await channel.push(0, 1, 2) - })().then(() => { - assert(false) - }).catch((reason) => { - assert.deepEqual(reason, new Error( - `Can't push more than one value at a time.`)) - }) + }, new Error(`Can't push more than one value at a time.`)) }) it(`returns a frozen promise`, function () { @@ -314,16 +303,29 @@ describe(`Channel object`, function () { }) }) - it(`reduce`, async function () { - assert.equal(await Channel.of(0, 1, 2) - .reduce((previous, current) => previous + current), - 3 - ) + describe(`reduce`, function () { + it(`callbackfn only`, async function () { + assert.equal(await Channel.of(0, 1, 2) + .reduce(Math.max), + 2 + ) + }) - assert.equal(await Channel.of(0, 1, 2) - .reduce((previous, current) => previous + current, 10), - 13 - ) + it(`initialValue`, async function () { + assert.equal(await Channel.of(0, 1, 2) + .reduce(Math.max, 10), + 10 + ) + }) + + it(`no values without initialValue`, function () { + return assertRejects( + async () => { + await Channel.of().reduce(Math.max) + }, + new TypeError(`No values in channel and initialValue wasn't provided.`) + ) + }) }) describe(`shift`, function () {