Replace JavaScript Standard Style with Prettier.

This commit is contained in:
David Braun 2017-10-16 13:24:12 -04:00
parent b9638f2113
commit d7355e17e1
No known key found for this signature in database
GPG key ID: 5694EEC4D129BDCF
9 changed files with 647 additions and 675 deletions

View file

@ -62,18 +62,22 @@ Imagine you're at a party and your next conversation depends on whom you run
into first: Alice, Bob, or Charlie. into first: Alice, Bob, or Charlie.
```JavaScript ```JavaScript
switch (await Channel.select(alice.shift(), bob.shift(), charlie.push(`Hi!`)) { switch (await Channel.select(
alice.shift(),
bob.shift(),
charlie.push(`Hi!`)
)) {
case alice: case alice:
console.log(`Alice said ${alice.value}.`) console.log(`Alice said ${alice.value}.`);
break break;
case bob: case bob:
console.log(`Bob said ${bob.value}.`) console.log(`Bob said ${bob.value}.`);
break break;
case charlie: case charlie:
console.log(`I said "hi" to Charlie.`) console.log(`I said "hi" to Charlie.`);
break break;
} }
``` ```
@ -81,55 +85,55 @@ Be careful of unintended side effects, however. Even though only one value is
pushed in the following example, the counter is incremented twice. pushed in the following example, the counter is incremented twice.
```JavaScript ```JavaScript
let counter = 0 let counter = 0;
const increment = () => { const increment = () => {
counter++ counter++;
return counter return counter;
} };
await Channel.select(alice.push(increment()), bob.push(increment())) await Channel.select(alice.push(increment()), bob.push(increment()));
assert.equal(counter, 2) assert.equal(counter, 2);
``` ```
Sometimes you don't want to wait until a method completes. You can use a closed Sometimes you don't want to wait until a method completes. You can use a closed
channel to return immediately even if no other channels are ready: channel to return immediately even if no other channels are ready:
```JavaScript ```JavaScript
const closed = Channel() const closed = Channel();
closed.close() closed.close();
switch (await Channel.select(alice.shift(), bob.shift(), closed.shift()) { switch (await Channel.select(alice.shift(), bob.shift(), closed.shift())) {
case alice: case alice:
console.log(`Alice said ${alice.value}.`) console.log(`Alice said ${alice.value}.`);
break break;
case bob: case bob:
console.log(`Bob said ${bob.value}.`) console.log(`Bob said ${bob.value}.`);
break break;
default: default:
console.log(`No one has anything to say yet.`) console.log(`No one has anything to say yet.`);
} }
``` ```
You can also arrange it so that the `select` completes within a timeout: You can also arrange it so that the `select` completes within a timeout:
```JavaScript ```JavaScript
const timeout = Channel() const timeout = Channel();
setTimeout(timeout.close, 1000) setTimeout(timeout.close, 1000);
switch (await Channel.select(alice.shift(), bob.shift(), timeout.shift()) { switch (await Channel.select(alice.shift(), bob.shift(), timeout.shift())) {
case alice: case alice:
console.log(`Alice said ${alice.value}.`) console.log(`Alice said ${alice.value}.`);
break break;
case bob: case bob:
console.log(`Bob said ${bob.value}.`) console.log(`Bob said ${bob.value}.`);
break break;
default: default:
console.log(`I stopped listening after one second.`) console.log(`I stopped listening after one second.`);
} }
``` ```
@ -198,15 +202,15 @@ argument.
The promise returned by `forEach` resolves when the channel is closed: The promise returned by `forEach` resolves when the channel is closed:
```JavaScript ```JavaScript
const toArray = async (channel) => { const toArray = async channel => {
const array = [] const array = [];
await channel.forEach((value) => { await channel.forEach(value => {
array.push(value) array.push(value);
}) });
return array return array;
} };
``` ```
If `callbackfn` is async then `forEach` will wait for it before iterating to the If `callbackfn` is async then `forEach` will wait for it before iterating to the
@ -214,9 +218,9 @@ next value:
```JavaScript ```JavaScript
const pipe = async (source, sink) => { const pipe = async (source, sink) => {
await source.forEach(sink.push) await source.forEach(sink.push);
sink.close() sink.close();
} };
``` ```
### join(separator) -> async String ### join(separator) -> async String
@ -286,15 +290,15 @@ in either of the following two ways:
```JavaScript ```JavaScript
// method // method
channel.slice(10) channel.slice(10);
// function // function
Channel.slice(10, Infinity, channel) Channel.slice(10, Infinity, channel);
``` ```
You can also use partial application to pass the channel in later: You can also use partial application to pass the channel in later:
```JavaScript ```JavaScript
const skipTen = Channel.slice(10, Infinity) const skipTen = Channel.slice(10, Infinity);
skipTen(channel) skipTen(channel);
``` ```

View file

@ -1,342 +1,332 @@
'use strict' "use strict";
// An order represents a pending push or shift. // An order represents a pending push or shift.
const Order = (channel) => { const Order = channel => {
let order let order;
const preonFulfilleds = [] const preonFulfilleds = [];
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
order = { order = {
resolve: (value) => { resolve: value => {
preonFulfilleds.forEach((preonFulfilled) => { preonFulfilleds.forEach(preonFulfilled => {
preonFulfilled(value) preonFulfilled(value);
}) });
resolve(value) resolve(value);
}, },
reject reject
} };
}) });
Object.assign(promise, { Object.assign(promise, {
cancel: () => { cancel: () => {
order.cancelled = true order.cancelled = true;
}, },
channel, channel,
prethen: (onFulfilled) => { prethen: onFulfilled => {
preonFulfilleds.push(onFulfilled) preonFulfilleds.push(onFulfilled);
} }
}) });
return {order, promise} return { order, promise };
} };
const prototype = {} const prototype = {};
const Channel = function (bufferLength = 0) { const Channel = function(bufferLength = 0) {
let buffered = 0 let buffered = 0;
let closed = false let closed = false;
let lastValue let lastValue;
let resolvedIndex = 0 let resolvedIndex = 0;
const pushes = [] const pushes = [];
const shifts = [] const shifts = [];
// Process the push and shift queues like an order book, looking for matches. // Process the push and shift queues like an order book, looking for matches.
const processOrders = () => { const processOrders = () => {
const index = {push: 0, shift: 0} const index = { push: 0, shift: 0 };
// Match pushes and shifts. // Match pushes and shifts.
while ((index.push < pushes.length) && (index.shift < shifts.length)) { while (index.push < pushes.length && index.shift < shifts.length) {
const push = pushes[index.push] const push = pushes[index.push];
const shift = shifts[index.shift] const shift = shifts[index.shift];
if (push.cancelled) { if (push.cancelled) {
index.push++ index.push++;
} else if (shift.cancelled) { } else if (shift.cancelled) {
index.shift++ index.shift++;
} else { } else {
lastValue = push.value lastValue = push.value;
shift.resolve(lastValue) shift.resolve(lastValue);
buffered = Math.max(0, buffered - 1) buffered = Math.max(0, buffered - 1);
index.push++ index.push++;
index.shift++ index.shift++;
} }
} }
// Resolve push promises up to the end of the buffer. // Resolve push promises up to the end of the buffer.
for ( for (
; ;
(resolvedIndex < index.push) || resolvedIndex < index.push ||
((resolvedIndex < pushes.length) && (buffered < bufferLength)); (resolvedIndex < pushes.length && buffered < bufferLength);
resolvedIndex++ resolvedIndex++
) { ) {
const {cancelled, resolve} = pushes[resolvedIndex] const { cancelled, resolve } = pushes[resolvedIndex];
if (!cancelled) { if (!cancelled) {
if (resolvedIndex > index.push) { if (resolvedIndex > index.push) {
buffered++ buffered++;
} }
resolve(bufferLength) resolve(bufferLength);
} }
} }
// If the channel is closed then resolve 'undefined' to remaining shifts. // If the channel is closed then resolve 'undefined' to remaining shifts.
if (closed) { if (closed) {
for (; for (; index.shift < shifts.length; index.shift++) {
index.shift < shifts.length; const { cancelled, resolve } = shifts[index.shift];
index.shift++
) {
const {cancelled, resolve} = shifts[index.shift]
if (!cancelled) { if (!cancelled) {
lastValue = undefined lastValue = undefined;
resolve(lastValue) resolve(lastValue);
} }
} }
} }
pushes.splice(0, index.push) pushes.splice(0, index.push);
shifts.splice(0, index.shift) shifts.splice(0, index.shift);
resolvedIndex -= index.push resolvedIndex -= index.push;
} };
const readOnly = Object.assign(Object.create(prototype), { const readOnly = Object.assign(Object.create(prototype), {
every: async (callbackfn, thisArg) => { every: async (callbackfn, thisArg) => {
for (;;) { for (;;) {
const value = await readOnly.shift() const value = await readOnly.shift();
if (value === undefined) { if (value === undefined) {
return true return true;
} else { } else {
if (!callbackfn.call(thisArg, value)) { if (!callbackfn.call(thisArg, value)) {
return false return false;
} }
} }
} }
}, },
filter: (callbackfn, thisArg) => { filter: (callbackfn, thisArg) => {
const output = Channel() const output = Channel();
(async () => {
;(async () => {
for (;;) { for (;;) {
const value = await readOnly.shift() const value = await readOnly.shift();
if (value === undefined) { if (value === undefined) {
await output.close() await output.close();
break break;
} else if (callbackfn.call(thisArg, value)) { } else if (callbackfn.call(thisArg, value)) {
await output.push(value) await output.push(value);
} }
} }
})() })();
return output return output;
}, },
forEach: async (callbackfn, thisArg) => { forEach: async (callbackfn, thisArg) => {
for (;;) { for (;;) {
const value = await readOnly.shift() const value = await readOnly.shift();
if (value === undefined) { if (value === undefined) {
break break;
} else { } else {
await callbackfn.call(thisArg, value) await callbackfn.call(thisArg, value);
} }
} }
}, },
join: async (separator) => { join: async separator => {
const elements = [] const elements = [];
await readOnly.forEach((element) => { await readOnly.forEach(element => {
elements.push(element) elements.push(element);
}) });
return elements.join(separator) return elements.join(separator);
}, },
map: (callbackfn, thisArg) => { map: (callbackfn, thisArg) => {
const output = Channel() const output = Channel();
(async () => {
;(async () => { await readOnly.forEach(value =>
await readOnly.forEach((value) =>
output.push(callbackfn.call(thisArg, value)) output.push(callbackfn.call(thisArg, value))
) );
output.close() output.close();
})() })();
return output return output;
}, },
readOnly: () => readOnly, readOnly: () => readOnly,
reduce: async (callbackfn, ...initialValue) => { reduce: async (callbackfn, ...initialValue) => {
let previousValue = initialValue[0] let previousValue = initialValue[0];
let previousValueDefined = initialValue.length > 0 let previousValueDefined = initialValue.length > 0;
await readOnly.forEach((currentValue) => { await readOnly.forEach(currentValue => {
if (previousValueDefined) { if (previousValueDefined) {
previousValue = callbackfn(previousValue, currentValue) previousValue = callbackfn(previousValue, currentValue);
} else { } else {
previousValue = currentValue previousValue = currentValue;
previousValueDefined = true previousValueDefined = true;
} }
}) });
if (previousValueDefined) { if (previousValueDefined) {
return previousValue return previousValue;
} else { } else {
throw new TypeError( throw new TypeError(
`No values in channel and initialValue wasn't provided.` `No values in channel and initialValue wasn't provided.`
) );
} }
}, },
shift: function () { shift: function() {
const {order, promise} = Order(this) const { order, promise } = Order(this);
shifts.push(order) shifts.push(order);
setImmediate(processOrders) setImmediate(processOrders);
return Object.freeze(promise) return Object.freeze(promise);
}, },
slice: (start, end = Infinity) => { slice: (start, end = Infinity) => {
const output = Channel() const output = Channel();
(async () => {
;(async () => {
for (let index = 0; index < end; index++) { for (let index = 0; index < end; index++) {
const value = await readOnly.shift() const value = await readOnly.shift();
if (value === undefined) { if (value === undefined) {
break break;
} else if (index >= start) { } else if (index >= start) {
await output.push(value) await output.push(value);
} }
} }
await output.close() await output.close();
})() })();
return output return output;
} }
}) });
Object.defineProperty(readOnly, `value`, {get: () => lastValue}) Object.defineProperty(readOnly, `value`, { get: () => lastValue });
Object.freeze(readOnly) Object.freeze(readOnly);
const writeOnly = Object.freeze({ const writeOnly = Object.freeze({
close: () => close: () =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (closed) { if (closed) {
reject(new Error(`Can't close an already-closed channel.`)) reject(new Error(`Can't close an already-closed channel.`));
} else { } else {
closed = true closed = true;
processOrders() processOrders();
// Give remaining orders in flight time to resolve before returning. // Give remaining orders in flight time to resolve before returning.
setImmediate(resolve) setImmediate(resolve);
} }
}), }),
push: function (value) { push: function(value) {
const {order, promise} = Order(this) const { order, promise } = Order(this);
order.value = value order.value = value;
if (closed) { if (closed) {
order.reject(new Error(`Can't push to closed channel.`)) order.reject(new Error(`Can't push to closed channel.`));
} else if (value === undefined) { } else if (value === undefined) {
order.reject(new TypeError( order.reject(
`Can't push 'undefined' to channel, use close instead.` new TypeError(`Can't push 'undefined' to channel, use close instead.`)
)) );
} else if (arguments.length > 1) { } else if (arguments.length > 1) {
order.reject(new Error(`Can't push more than one value at a time.`)) order.reject(new Error(`Can't push more than one value at a time.`));
} else { } else {
pushes.push(order) pushes.push(order);
setImmediate(processOrders) setImmediate(processOrders);
} }
return Object.freeze(promise) return Object.freeze(promise);
}, },
writeOnly: () => writeOnly writeOnly: () => writeOnly
}) });
// Use Object.create because readOnly has a getter. // Use Object.create because readOnly has a getter.
return Object.freeze(Object.assign(Object.create(readOnly), writeOnly)) return Object.freeze(Object.assign(Object.create(readOnly), writeOnly));
} };
Channel.from = (values) => { Channel.from = values => {
const channel = Channel() const channel = Channel();
(async () => {
;(async () => {
try { try {
// iterable // iterable
for (let value of values) { for (let value of values) {
await channel.push(value) await channel.push(value);
} }
await channel.close() await channel.close();
} catch (exception) { } catch (exception) {
// Assume it's a Node.js stream.readable. // Assume it's a Node.js stream.readable.
values.on('readable', async () => { values.on("readable", async () => {
while (true) { while (true) {
const data = values.read() const data = values.read();
if (data === null) { if (data === null) {
break break;
} else { } else {
await channel.push(data) await channel.push(data);
} }
} }
}) });
values.once('end', channel.close) values.once("end", channel.close);
} }
})() })();
return channel return channel;
} };
Channel.of = (...values) => Channel.of = (...values) => Channel.from(values);
Channel.from(values)
Channel.isChannel = (arg) => Channel.isChannel = arg => prototype.isPrototypeOf(arg);
prototype.isPrototypeOf(arg)
Channel.select = (...methodPromises) => Channel.select = (...methodPromises) =>
Object.assign( Object.assign(
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
methodPromises.forEach(async (promise) => { methodPromises.forEach(async promise => {
promise.prethen(() => { promise.prethen(() => {
// We've been given a heads-up that this method will complete first so // We've been given a heads-up that this method will complete first so
// cancel the other method calls. // cancel the other method calls.
methodPromises.forEach((other) => { methodPromises.forEach(other => {
if (other !== promise) { if (other !== promise) {
other.cancel() other.cancel();
} }
}) });
}) });
try { try {
await promise await promise;
} catch (exception) { } catch (exception) {
reject(exception) reject(exception);
} }
resolve(promise.channel) resolve(promise.channel);
}) });
}), }),
{ {
cancel: () => cancel: () => Promise.all(methodPromises.map(promise => promise.cancel()))
Promise.all(methodPromises.map((promise) => promise.cancel()))
} }
) );
// functional interface allowing full or partial application // functional interface allowing full or partial application
// //
@ -347,19 +337,17 @@ Channel.select = (...methodPromises) =>
// const skipTen = Channel.slice(10, Infinity) // const skipTen = Channel.slice(10, Infinity)
// skipTen(channel) // skipTen(channel)
const channel = Channel() const channel = Channel();
const methods = Object.keys(channel).concat(Object.keys(channel.readOnly())) const methods = Object.keys(channel).concat(Object.keys(channel.readOnly()));
methods.forEach((method) => { methods.forEach(method => {
Channel[method] = (...args) => { Channel[method] = (...args) => {
const arity = method === `slice` const arity = method === `slice` ? 3 : channel[method].length;
? 3
: channel[method].length
return args.length >= arity return args.length >= arity
? args[arity - 1][method](...args.slice(0, arity - 1)) ? args[arity - 1][method](...args.slice(0, arity - 1))
: (channel) => channel[method](...args) : channel => channel[method](...args);
} };
}) });
module.exports = Object.freeze(Channel) module.exports = Object.freeze(Channel);

View file

@ -15,7 +15,7 @@
"@nodeguy/assert": "0.1.1", "@nodeguy/assert": "0.1.1",
"husky": "0.14.3", "husky": "0.14.3",
"mocha": "3.5.3", "mocha": "3.5.3",
"standard": "10.0.3" "prettier": "1.7.4"
}, },
"homepage": "https://github.com/NodeGuy/channel", "homepage": "https://github.com/NodeGuy/channel",
"keywords": [ "keywords": [
@ -30,8 +30,7 @@
"url": "git+https://github.com/NodeGuy/channel.git" "url": "git+https://github.com/NodeGuy/channel.git"
}, },
"scripts": { "scripts": {
"precommit": "standard", "precommit": "prettier --write lib/**/*.js test/**/*.js",
"pretest": "standard",
"test": "mocha --recursive" "test": "mocha --recursive"
}, },
"standard": { "standard": {

View file

@ -5,73 +5,73 @@
// Test the situation in which two cases of a select can // Test the situation in which two cases of a select can
// both end up running. See http://codereview.appspot.com/180068. // both end up running. See http://codereview.appspot.com/180068.
'use strict' "use strict";
const Channel = require('../../lib') const Channel = require("../../lib");
it(`doubleselect`, async function () { it(`doubleselect`, async function() {
this.timeout(10 * 1000) this.timeout(10 * 1000);
const iterations = 100000 // number of iterations const iterations = 100000; // number of iterations
// sender sends a counter to one of four different channels. If two cases both // sender sends a counter to one of four different channels. If two cases both
// end up running in the same iteration, the same value will be sent to two // end up running in the same iteration, the same value will be sent to two
// different channels. // different channels.
const sender = async (n, c1, c2, c3, c4) => { const sender = async (n, c1, c2, c3, c4) => {
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
await Channel.select(c1.push(i), c2.push(i), c3.push(i), c4.push(i)) await Channel.select(c1.push(i), c2.push(i), c3.push(i), c4.push(i));
} }
c1.close() c1.close();
c2.close() c2.close();
c3.close() c3.close();
c4.close() c4.close();
} };
// mux receives the values from sender and forwards them onto another channel. // mux receives the values from sender and forwards them onto another channel.
// It would be simpler to just have sender's four cases all be the same // It would be simpler to just have sender's four cases all be the same
// channel, but this doesn't actually trigger the bug. // channel, but this doesn't actually trigger the bug.
const mux = async (output, input, done) => { const mux = async (output, input, done) => {
await input.forEach(async (value) => { await input.forEach(async value => {
await output.push(value) await output.push(value);
}) });
await done.push(true) await done.push(true);
} };
// recver gets a steam of values from the four mux's and checks for // recver gets a steam of values from the four mux's and checks for
// duplicates. // duplicates.
const recver = (input) => { const recver = input => {
const seen = new Map() const seen = new Map();
input.forEach((v) => { input.forEach(v => {
if (seen.has(v)) { if (seen.has(v)) {
throw new Error(`got duplicate value: ${v}`) throw new Error(`got duplicate value: ${v}`);
} }
seen.set(v, true) seen.set(v, true);
}) });
} };
const c1 = Channel() const c1 = Channel();
const c2 = Channel() const c2 = Channel();
const c3 = Channel() const c3 = Channel();
const c4 = Channel() const c4 = Channel();
const done = Channel() const done = Channel();
const cmux = Channel() const cmux = Channel();
sender(iterations, c1, c2, c3, c4) sender(iterations, c1, c2, c3, c4);
mux(cmux, c1, done) mux(cmux, c1, done);
mux(cmux, c2, done) mux(cmux, c2, done);
mux(cmux, c3, done) mux(cmux, c3, done);
mux(cmux, c4, done) mux(cmux, c4, done);
// We keep the recver because it might catch more bugs in the future. // We keep the recver because it might catch more bugs in the future.
// However, the result of the bug linked to at the top is that we'll // However, the result of the bug linked to at the top is that we'll
// end up panicking with: "throw: bad g->status in ready". // end up panicking with: "throw: bad g->status in ready".
recver(cmux) recver(cmux);
await done.shift() await done.shift();
await done.shift() await done.shift();
await done.shift() await done.shift();
await done.shift() await done.shift();
cmux.close() cmux.close();
}) });

View file

@ -4,58 +4,58 @@
// Test that unbuffered channels act as pure fifos. // Test that unbuffered channels act as pure fifos.
'use strict' "use strict";
const Channel = require('../../lib') const Channel = require("../../lib");
it(`fifo`, function () { it(`fifo`, function() {
const N = 10 const N = 10;
const AsynchFifo = async () => { const AsynchFifo = async () => {
const ch = Channel(10) const ch = Channel(10);
for (let i = 0; i < N; i++) { for (let i = 0; i < N; i++) {
await ch.push(i) await ch.push(i);
} }
for (let i = 0; i < N; i++) { for (let i = 0; i < N; i++) {
if (await ch.shift() !== i) { if ((await ch.shift()) !== i) {
throw new Error(`bad receive`) throw new Error(`bad receive`);
}
} }
} }
};
const Chain = async (ch, val, input, output) => { const Chain = async (ch, val, input, output) => {
await input.shift() await input.shift();
if (await ch.shift() !== val) { if ((await ch.shift()) !== val) {
throw new Error(val) throw new Error(val);
} }
await output.push(1) await output.push(1);
} };
// thread together a daisy chain to read the elements in sequence // thread together a daisy chain to read the elements in sequence
const SynchFifo = async () => { const SynchFifo = async () => {
const ch = Channel() const ch = Channel();
let input = Channel() let input = Channel();
let start = input let start = input;
for (let i = 0; i < N; i++) { for (let i = 0; i < N; i++) {
const output = Channel() const output = Channel();
Chain(ch, i, input, output) Chain(ch, i, input, output);
input = output input = output;
} }
await start.push(0) await start.push(0);
for (let i = 0; i < N; i++) { for (let i = 0; i < N; i++) {
await ch.push(i) await ch.push(i);
} }
await input.shift() await input.shift();
} };
AsynchFifo() AsynchFifo();
SynchFifo() SynchFifo();
}) });

View file

@ -5,29 +5,29 @@
// Torture test for goroutines. // Torture test for goroutines.
// Make a lot of goroutines, threaded together, and tear them down cleanly. // Make a lot of goroutines, threaded together, and tear them down cleanly.
'use strict' "use strict";
const Channel = require('../../lib') const Channel = require("../../lib");
it(`goroutines`, async function () { it(`goroutines`, async function() {
const f = async (left, right) => { const f = async (left, right) => {
await left.push(await right.shift()) await left.push(await right.shift());
} };
const n = 10000 const n = 10000;
const leftmost = Channel() const leftmost = Channel();
let right = leftmost let right = leftmost;
let left = leftmost let left = leftmost;
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
right = Channel() right = Channel();
f(left, right) f(left, right);
left = right left = right;
} }
;(async (c) => { (async c => {
await c.push(1) await c.push(1);
})(right) })(right);
await leftmost.shift() await leftmost.shift();
}) });

View file

@ -7,72 +7,72 @@
// Test various correct and incorrect permutations of send-only, // Test various correct and incorrect permutations of send-only,
// receive-only, and bidirectional channels. // receive-only, and bidirectional channels.
'use strict' "use strict";
const assert = require(`@nodeguy/assert`) const assert = require(`@nodeguy/assert`);
const Channel = require('../../lib') const Channel = require("../../lib");
it(`perm`, function () { it(`perm`, function() {
const c = Channel() const c = Channel();
const cr = Channel().readOnly() const cr = Channel().readOnly();
const cs = Channel().writeOnly() const cs = Channel().writeOnly();
const n = 0 const n = 0;
assert.throws(() => { assert.throws(() => {
Channel.shift(n) // ERROR "receive from non-chan" Channel.shift(n); // ERROR "receive from non-chan"
}) });
assert.throws(() => { assert.throws(() => {
Channel.push(2, n) // ERROR "send to non-chan" Channel.push(2, n); // ERROR "send to non-chan"
}) });
c.push(0) // ok c.push(0); // ok
c.shift() // ok c.shift(); // ok
assert.throws(() => { assert.throws(() => {
cr.push(0) // ERROR "send" cr.push(0); // ERROR "send"
}) });
cr.shift() // ok cr.shift(); // ok
cs.push(0) // ok cs.push(0); // ok
assert.throws(() => { assert.throws(() => {
cs.shift() // ERROR "receive" cs.shift(); // ERROR "receive"
}) });
Channel.select( Channel.select(
c.push(0), // ok c.push(0), // ok
c.shift() // ok c.shift() // ok
) );
assert.throws(() => { assert.throws(() => {
Channel.select( Channel.select(
cr.push(0) // ERROR "send" cr.push(0) // ERROR "send"
) );
}) });
Channel.select(cr.shift()) // ok Channel.select(cr.shift()); // ok
Channel.select(cs.push(0)) // ok Channel.select(cs.push(0)); // ok
assert.throws(() => { assert.throws(() => {
Channel.select(cs.shift()) // ERROR "receive" Channel.select(cs.shift()); // ERROR "receive"
}) });
assert.throws(() => { assert.throws(() => {
cs.forEach(() => {}) // ERROR "receive" cs.forEach(() => {}); // ERROR "receive"
}) });
c.close() c.close();
cs.close() cs.close();
assert.throws(() => { assert.throws(() => {
cr.close() // ERROR "receive" cr.close(); // ERROR "receive"
}) });
assert.throws(() => { assert.throws(() => {
Channel.close(n) // ERROR "invalid operation.*non-chan type" Channel.close(n); // ERROR "invalid operation.*non-chan type"
}) });
}) });

View file

@ -4,70 +4,73 @@
// Test simple select. // Test simple select.
'use strict' "use strict";
const Channel = require('../../lib') const Channel = require("../../lib");
it(`select`, async function () { it(`select`, async function() {
const closed = Channel() const closed = Channel();
closed.close() closed.close();
let counter = 0 let counter = 0;
let shift = 0 let shift = 0;
const GetValue = () => { const GetValue = () => {
counter++ counter++;
return 1 << shift return 1 << shift;
} };
const Send = async (a, b) => { const Send = async (a, b) => {
let done = false let done = false;
let i = 0 let i = 0;
do { do {
switch (await Channel.select(a.push(GetValue()), b.push(GetValue()), switch (await Channel.select(
closed.shift())) { a.push(GetValue()),
b.push(GetValue()),
closed.shift()
)) {
case a: case a:
i++ i++;
a = Channel() a = Channel();
break break;
case b: case b:
i++ i++;
b = Channel() b = Channel();
break break;
default: default:
done = true done = true;
} }
shift++ shift++;
} while (!done) } while (!done);
return i return i;
} };
let a = Channel(1) let a = Channel(1);
let b = Channel(1) let b = Channel(1);
let v = await Send(a, b) let v = await Send(a, b);
if (v !== 2) { if (v !== 2) {
throw new Error(`Send returned ${v} !== 2`) throw new Error(`Send returned ${v} !== 2`);
} }
const av = await a.shift() const av = await a.shift();
const bv = await b.shift() const bv = await b.shift();
if ((av | bv) !== 3) { if ((av | bv) !== 3) {
throw new Error(`bad values ${av} ${bv}`) throw new Error(`bad values ${av} ${bv}`);
} }
v = await Send(a, Channel()) v = await Send(a, Channel());
if (v !== 1) { if (v !== 1) {
throw new Error(`Send returned ${v} !== 1`) throw new Error(`Send returned ${v} !== 1`);
} }
if (counter !== 10) { if (counter !== 10) {
throw new Error(`counter is ${counter} !== 10`) throw new Error(`counter is ${counter} !== 10`);
} }
}) });

View file

@ -1,415 +1,393 @@
'use strict' "use strict";
const assert = require('@nodeguy/assert') const assert = require("@nodeguy/assert");
const Channel = require('../lib') const Channel = require("../lib");
const stream = require('stream') const stream = require("stream");
const assertRejects = async (callback, reason) => { const assertRejects = async (callback, reason) => {
try { try {
await callback() await callback();
} catch (exception) { } catch (exception) {
if (reason) { if (reason) {
assert.deepEqual(exception, reason) assert.deepEqual(exception, reason);
} }
return return;
} }
assert.fail(null, reason, `Missing expected rejection.`) assert.fail(null, reason, `Missing expected rejection.`);
} };
const toArray = async (channel) => { const toArray = async channel => {
const array = [] const array = [];
await channel.forEach((item) => { await channel.forEach(item => {
array.push(item) array.push(item);
}) });
return array return array;
} };
describe(`Channel`, function () { describe(`Channel`, function() {
it(`allows the use of new`, function () { it(`allows the use of new`, function() {
return new Channel() return new Channel();
}) });
it(`is frozen`, function () { it(`is frozen`, function() {
assert.throws(() => { assert.throws(() => {
Channel.frozen = false Channel.frozen = false;
}) });
}) });
it(`creates a frozen object`, function () { it(`creates a frozen object`, function() {
assert.throws(() => { assert.throws(() => {
Channel().frozen = false Channel().frozen = false;
}) });
}) });
it(`creates a buffered channel`, async function () { it(`creates a buffered channel`, async function() {
const channel = Channel(2) const channel = Channel(2);
(async () => {
assert.equal(await channel.shift(), 0);
})();
;(async () => { await channel.push(0);
assert.equal(await channel.shift(), 0) await channel.push(1);
})() await channel.push(2);
});
await channel.push(0) describe(`from`, function() {
await channel.push(1) it(`iterable`, async function() {
await channel.push(2) assert.deepEqual(await toArray(Channel.from([0, 1, 2])), [0, 1, 2]);
}) });
describe(`from`, function () { it(`Node.js's stream.readOnly`, async function() {
it(`iterable`, async function () { const readOnly = stream.PassThrough({ objectMode: true });
assert.deepEqual(await toArray(Channel.from([0, 1, 2])), [0, 1, 2]) readOnly.write(0);
}) readOnly.write(1);
readOnly.end(2);
assert.deepEqual(await toArray(Channel.from(readOnly)), [0, 1, 2]);
});
});
it(`Node.js's stream.readOnly`, async function () { it(`isChannel`, function() {
const readOnly = stream.PassThrough({objectMode: true}) assert(Channel.isChannel(Channel.of(0, 1, 2)));
readOnly.write(0) assert(!Channel.isChannel(Array.of(0, 1, 2)));
readOnly.write(1) });
readOnly.end(2)
assert.deepEqual(await toArray(Channel.from(readOnly)), [0, 1, 2])
})
})
it(`isChannel`, function () { it(`of`, async function() {
assert(Channel.isChannel(Channel.of(0, 1, 2))) assert.deepEqual(await toArray(Channel.of(0, 1, 2)), [0, 1, 2]);
assert(!Channel.isChannel(Array.of(0, 1, 2))) });
})
it(`of`, async function () { describe(`select`, function() {
assert.deepEqual(await toArray(Channel.of(0, 1, 2)), [0, 1, 2]) it(`miscellaneous`, async function() {
}) const a = Channel();
const b = Channel();
(async () => {
await b.push(0);
await a.push(1);
await a.shift();
})();
describe(`select`, function () { assert.equal(await Channel.select(a.shift(), b.shift()), b);
it(`miscellaneous`, async function () { assert.equal(b.value, 0);
const a = Channel() assert.equal(await a.shift(), 1);
const b = Channel() assert.equal(await Channel.select(a.push(0), b.shift()), a);
});
});
;(async () => { it(`allows for non-blocking selects`, async function() {
await b.push(0) const a = Channel();
await a.push(1) const b = Channel();
await a.shift() const nonBlocking = Channel();
})() nonBlocking.close();
assert.equal(await Channel.select(a.shift(), b.shift()), b)
assert.equal(b.value, 0)
assert.equal(await a.shift(), 1)
assert.equal(await Channel.select(a.push(0), b.shift()), a)
})
})
it(`allows for non-blocking selects`, async function () {
const a = Channel()
const b = Channel()
const nonBlocking = Channel()
nonBlocking.close()
switch (await Channel.select(a.shift(), b.push(0), nonBlocking.shift())) { switch (await Channel.select(a.shift(), b.push(0), nonBlocking.shift())) {
case a: case a:
assert(false) assert(false);
break break;
case b: case b:
assert(false) assert(false);
break break;
default: default:
assert(true) assert(true);
break break;
} }
}) });
it(`cancel`, async function () { it(`cancel`, async function() {
const channel = Channel() const channel = Channel();
Channel.select(channel.push(`cancelled`)).cancel() Channel.select(channel.push(`cancelled`)).cancel();
const closed = Channel.of() const closed = Channel.of();
assert.equal(await Channel.select(channel.shift(), closed.shift()), closed) assert.equal(await Channel.select(channel.shift(), closed.shift()), closed);
}) });
}) });
describe(`functional interface`, async function () { describe(`functional interface`, async function() {
describe(`map`, function () { describe(`map`, function() {
it(`full application`, async function () { it(`full application`, async function() {
assert.deepEqual( assert.deepEqual(
await toArray(Channel.map( await toArray(
(value) => value.toUpperCase(), Channel.map(value => value.toUpperCase(), Channel.of(`a`, `b`, `c`))
Channel.of(`a`, `b`, `c`)
)),
[`A`, `B`, `C`]
)
})
it(`partial application`, async function () {
assert.deepEqual(
await toArray(Channel.map((value) =>
value.toUpperCase())(Channel.of(`a`, `b`, `c`))
), ),
[`A`, `B`, `C`] [`A`, `B`, `C`]
) );
}) });
})
describe(`slice`, function () { it(`partial application`, async function() {
it(`full application`, async function () { assert.deepEqual(
await toArray(
Channel.map(value => value.toUpperCase())(Channel.of(`a`, `b`, `c`))
),
[`A`, `B`, `C`]
);
});
});
describe(`slice`, function() {
it(`full application`, async function() {
assert.deepEqual( assert.deepEqual(
await toArray(Channel.slice(1, 4, Channel.of(0, 1, 2, 3, 4))), await toArray(Channel.slice(1, 4, Channel.of(0, 1, 2, 3, 4))),
[1, 2, 3] [1, 2, 3]
) );
}) });
it(`partial application`, async function () { it(`partial application`, async function() {
assert.deepEqual( assert.deepEqual(
await toArray(Channel.slice(1, 4)(Channel.of(0, 1, 2, 3, 4))), await toArray(Channel.slice(1, 4)(Channel.of(0, 1, 2, 3, 4))),
[1, 2, 3] [1, 2, 3]
) );
}) });
}) });
}) });
describe(`Channel object`, function () { describe(`Channel object`, function() {
describe(`close`, function () { describe(`close`, function() {
it(`can't close an already closed channel`, function () { it(`can't close an already closed channel`, function() {
const channel = Channel() const channel = Channel();
channel.close() channel.close();
return assertRejects(async () => { return assertRejects(async () => {
await channel.close() await channel.close();
}, new Error(`Can't close an already-closed channel.`)) }, new Error(`Can't close an already-closed channel.`));
}) });
it(`can't push to a closed channel`, async function () { it(`can't push to a closed channel`, async function() {
const channel = Channel() const channel = Channel();
channel.close() channel.close();
return assertRejects(async () => { return assertRejects(async () => {
await channel.push(0) await channel.push(0);
}, new Error(`Can't push to closed channel.`)) }, new Error(`Can't push to closed channel.`));
}) });
it(`returns 'undefined' immediately from shift`, async function () { it(`returns 'undefined' immediately from shift`, async function() {
const channel = Channel() const channel = Channel();
channel.close() channel.close();
assert.strictEqual(await channel.shift(), undefined) assert.strictEqual(await channel.shift(), undefined);
}) });
}) });
it(`every`, async function () { it(`every`, async function() {
const even = (number) => number % 2 === 0 const even = number => number % 2 === 0;
assert(!await Channel.of(0, 1, 2).every(even)) assert(!await Channel.of(0, 1, 2).every(even));
assert(await Channel.of(0, 2, 4).every(even)) assert(await Channel.of(0, 2, 4).every(even));
}) });
it(`filter`, async function () { it(`filter`, async function() {
assert.deepEqual( assert.deepEqual(
await toArray(Channel.of(0, 1, 2, 3, 4, 5) await toArray(
.filter((value) => value % 2 !== 0) Channel.of(0, 1, 2, 3, 4, 5).filter(value => value % 2 !== 0)
), ),
[1, 3, 5] [1, 3, 5]
) );
}) });
it(`forEach`, async function () { it(`forEach`, async function() {
const output = Channel() const output = Channel();
(async () => {
await Channel.of(0, 1, 2).forEach(output.push);
output.close();
})();
;(async () => { assert.deepEqual(await toArray(output), [0, 1, 2]);
await Channel.of(0, 1, 2).forEach(output.push) });
output.close()
})()
assert.deepEqual(await toArray(output), [0, 1, 2]) it(`join`, async function() {
}) assert.equal(await Channel.of(`a`, `b`, `c`).join(), `a,b,c`);
});
it(`join`, async function () { it(`map`, async function() {
assert.equal(await Channel.of(`a`, `b`, `c`).join(), `a,b,c`)
})
it(`map`, async function () {
assert.deepEqual( assert.deepEqual(
await toArray(Channel.of(`a`, `b`, `c`) await toArray(
.map((value) => value.toUpperCase()) Channel.of(`a`, `b`, `c`).map(value => value.toUpperCase())
), ),
[`A`, `B`, `C`] [`A`, `B`, `C`]
) );
}) });
describe(`push`, function () { describe(`push`, function() {
it(`with shift`, async function () { it(`with shift`, async function() {
const channel = Channel() const channel = Channel();
(async () => {
await channel.push(0);
})();
;(async () => { assert.equal(await channel.shift(), 0);
await channel.push(0) });
})()
assert.equal(await channel.shift(), 0) describe(`undefined`, function() {
}) it(`outside select`, function() {
const channel = Channel();
describe(`undefined`, function () {
it(`outside select`, function () {
const channel = Channel()
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 assertRejects(async () => { return assertRejects(async () => {
await Channel.select(channel.push(undefined)) await channel.push(undefined);
}, new TypeError( }, new TypeError(`Can't push 'undefined' to channel, use close instead.`));
`Can't push 'undefined' to channel, use close instead.` });
))
})
})
it(`disallows multiple values`, function () { it(`inside select`, function() {
const channel = Channel() const channel = Channel();
return assertRejects(async () => { return assertRejects(async () => {
await channel.push(0, 1, 2) await Channel.select(channel.push(undefined));
}, new Error(`Can't push more than one value at a time.`)) }, new TypeError(`Can't push 'undefined' to channel, use close instead.`));
}) });
});
it(`returns a frozen promise`, function () { it(`disallows multiple values`, function() {
const channel = Channel();
return assertRejects(async () => {
await channel.push(0, 1, 2);
}, new Error(`Can't push more than one value at a time.`));
});
it(`returns a frozen promise`, function() {
assert.throws(() => { assert.throws(() => {
Channel().push(0).frozen = false Channel().push(0).frozen = false;
}) });
}) });
}) });
it(`readOnly`, async function () { it(`readOnly`, async function() {
const channel = Channel() const channel = Channel();
const readOnly = channel.readOnly() const readOnly = channel.readOnly();
assert.throws(() => { assert.throws(() => {
readOnly.close() readOnly.close();
}) });
assert.throws(() => { assert.throws(() => {
readOnly.push(0) readOnly.push(0);
}) });
assert.throws(() => { assert.throws(() => {
readOnly.writeOnly() readOnly.writeOnly();
}) });
(async () => {
await channel.push(1);
})();
;(async () => { assert.equal(readOnly.readOnly(), readOnly);
await channel.push(1) assert.equal(await readOnly.shift(), 1);
})() assert.equal(readOnly.value, 1);
assert.equal(readOnly.readOnly(), readOnly)
assert.equal(await readOnly.shift(), 1)
assert.equal(readOnly.value, 1)
assert.throws(() => { assert.throws(() => {
readOnly.frozen = false readOnly.frozen = false;
}) });
}) });
describe(`reduce`, function () { describe(`reduce`, function() {
it(`callbackfn only`, async function () { it(`callbackfn only`, async function() {
assert.equal(await Channel.of(0, 1, 2) assert.equal(await Channel.of(0, 1, 2).reduce(Math.max), 2);
.reduce(Math.max), });
2
)
})
it(`initialValue`, async function () { it(`initialValue`, async function() {
assert.equal(await Channel.of(0, 1, 2) assert.equal(await Channel.of(0, 1, 2).reduce(Math.max, 10), 10);
.reduce(Math.max, 10), });
10
)
})
it(`no values without initialValue`, function () { it(`no values without initialValue`, function() {
return assertRejects( return assertRejects(async () => {
async () => { await Channel.of().reduce(Math.max);
await Channel.of().reduce(Math.max) }, new TypeError(`No values in channel and initialValue wasn't provided.`));
}, });
new TypeError(`No values in channel and initialValue wasn't provided.`) });
)
})
})
describe(`shift`, function () { describe(`shift`, function() {
it(`with push`, async function () { it(`with push`, async function() {
const channel = Channel() const channel = Channel();
(async () => {
await channel.push(0);
})();
;(async () => { assert.equal(await channel.shift(), 0);
await channel.push(0) });
})()
assert.equal(await channel.shift(), 0) it(`returns a frozen promise`, function() {
})
it(`returns a frozen promise`, function () {
assert.throws(() => { assert.throws(() => {
Channel().shift().frozen = false Channel().shift().frozen = false;
}) });
}) });
}) });
describe(`slice`, function () { describe(`slice`, function() {
it(`start`, async function () { it(`start`, async function() {
assert.deepEqual(await toArray(Channel.of(0, 1, 2).slice(1)), [1, 2]) assert.deepEqual(await toArray(Channel.of(0, 1, 2).slice(1)), [1, 2]);
}) });
it(`end`, async function () { it(`end`, async function() {
assert.deepEqual( assert.deepEqual(await toArray(Channel.of(0, 1, 2, 3, 4).slice(1, 4)), [
await toArray(Channel.of(0, 1, 2, 3, 4).slice(1, 4)), 1,
[1, 2, 3] 2,
) 3
}) ]);
}) });
});
it(`value`, async function () { it(`value`, async function() {
const channel = Channel() const channel = Channel();
(async () => {
await channel.push(0);
})();
;(async () => { await channel.shift();
await channel.push(0) assert.equal(channel.value, 0);
})()
await channel.shift()
assert.equal(channel.value, 0)
assert.throws(() => { assert.throws(() => {
channel.value = 1 channel.value = 1;
}) });
channel.close() channel.close();
await channel.shift() await channel.shift();
assert.equal(channel.value, undefined) assert.equal(channel.value, undefined);
}) });
describe(`writeOnly`, function () { describe(`writeOnly`, function() {
it(`provides only write methods`, async function () { it(`provides only write methods`, async function() {
const channel = Channel() const channel = Channel();
const writeOnly = channel.writeOnly() const writeOnly = channel.writeOnly();
assert.throws(() => { assert.throws(() => {
writeOnly.readOnly() writeOnly.readOnly();
}) });
assert.throws(() => { assert.throws(() => {
writeOnly.shift() writeOnly.shift();
}) });
assert.equal(writeOnly.value, undefined) assert.equal(writeOnly.value, undefined);
(async () => {
await channel.shift();
})();
;(async () => { await writeOnly.push(0);
await channel.shift() writeOnly.close();
})()
await writeOnly.push(0)
writeOnly.close()
assert.throws(() => { assert.throws(() => {
writeOnly.frozen = false writeOnly.frozen = false;
}) });
}) });
}) });
}) });