Add flat
and flatMap
.
This commit is contained in:
parent
e9023771cc
commit
6b1c55cff3
5 changed files with 129 additions and 62 deletions
|
@ -5,7 +5,7 @@ module.exports = {
|
||||||
node: true
|
node: true
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2017
|
ecmaVersion: 2018
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
quotes: ["error", "backtick"],
|
quotes: ["error", "backtick"],
|
||||||
|
|
32
README.md
32
README.md
|
@ -1,25 +1,21 @@
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
This is an idiomatic, minimally-opinionated `Channel` type for JavaScript that's
|
This is an idiomatic, minimally-opinionated `Channel` type for JavaScript that's
|
||||||
inspired by [Go's channels](https://golang.org/ref/spec#Channel_types). It
|
inspired by [Go's channels](https://golang.org/ref/spec#Channel_types). It
|
||||||
works in browsers and in Node.js. If you know how to use an `Array` then you
|
works in browsers and in Node.js. If you know how to use an `Array` then you
|
||||||
already know most of how to use a `Channel`.
|
already know most of how to use a `Channel`.
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
Go's use of channels for concurrency is amazing and with JavaScript's
|
Go's use of channels for concurrency is amazing and with JavaScript's
|
||||||
async/await feature we have the basis for it as well. All that's missing is a
|
async/await feature we have the basis for it as well. All that's missing is a
|
||||||
solid `Channel` type. There are existing libraries but I wanted an idiomatic
|
solid `Channel` type. There are existing libraries but I wanted an idiomatic
|
||||||
`Channel` type that's simple and minimally-opinionated.
|
`Channel` type that's simple and minimally-opinionated.
|
||||||
|
|
||||||
This document assumes you're familiar with Go's channels and why you'd want to
|
This document assumes you're familiar with Go's channels and why you'd want to
|
||||||
use them. For explanatory background, see my [blog
|
use them. For explanatory background, see my [blog
|
||||||
article](https://www.nodeguy.com/channels-for-javascript/) on the subject.
|
article](https://www.nodeguy.com/channels-for-javascript/) on the subject.
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
ES 2017
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -30,8 +26,8 @@ $ npm install @nodeguy/channel
|
||||||
|
|
||||||
Create a channel with `Channel()`.
|
Create a channel with `Channel()`.
|
||||||
|
|
||||||
To send a value to a channel use `push`. To receive a value from a channel use
|
To send a value to a channel use `push`. To receive a value from a channel use
|
||||||
`shift`. Always precede the method calls with `await`. Close the channel when
|
`shift`. Always precede the method calls with `await`. Close the channel when
|
||||||
there are no more values to push.
|
there are no more values to push.
|
||||||
|
|
||||||
```JavaScript
|
```JavaScript
|
||||||
|
@ -64,19 +60,19 @@ The [API](doc/API.md) is in the `doc` directory.
|
||||||
|
|
||||||
# Similar Projects
|
# Similar Projects
|
||||||
|
|
||||||
* [Channel](https://github.com/gozala/channel)
|
- [Channel](https://github.com/gozala/channel)
|
||||||
* [cochan](https://github.com/skozin/cochan)
|
- [cochan](https://github.com/skozin/cochan)
|
||||||
* [js-csp](https://github.com/ubolonton/js-csp)
|
- [js-csp](https://github.com/ubolonton/js-csp)
|
||||||
* [node-csp](https://github.com/olahol/node-csp)
|
- [node-csp](https://github.com/olahol/node-csp)
|
||||||
|
|
||||||
# Copyright
|
# Copyright
|
||||||
|
|
||||||
Copyright 2017 [David Braun](https://www.NodeGuy.com/)
|
Copyright 2017 [David Braun](https://www.NodeGuy.com/)
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
these files except in compliance with the License. You may obtain a copy of the
|
these files except in compliance with the License. You may obtain a copy of the
|
||||||
License at `http://www.apache.org/licenses/LICENSE-2.0`. Unless required by
|
License at `http://www.apache.org/licenses/LICENSE-2.0`. Unless required by
|
||||||
applicable law or agreed to in writing, software distributed under the License
|
applicable law or agreed to in writing, software distributed under the License
|
||||||
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
KIND, either express or implied. See the License for the specific language
|
KIND, either express or implied. See the License for the specific language
|
||||||
governing permissions and limitations under the License.
|
governing permissions and limitations under the License.
|
||||||
|
|
98
doc/API.md
98
doc/API.md
|
@ -1,35 +1,37 @@
|
||||||
<!-- TOC -->
|
<!-- TOC -->
|
||||||
|
|
||||||
* [New Properties](#new-properties)
|
- [New Properties](#new-properties)
|
||||||
* [close() -> (async)](#close---async)
|
- [close() -> (async)](#close-async)
|
||||||
* [readOnly() -> Channel](#readonly---channel)
|
- [readOnly() -> Channel](#readonly-channel)
|
||||||
* [writeOnly() -> Channel](#writeonly---channel)
|
- [writeOnly() -> Channel](#writeonly-channel)
|
||||||
* [Channel.select(promises) -> (async) channel](#channelselectpromises---async-channel)
|
- [Channel.select(promises) -> (async) channel](#channelselectpromises-async-channel)
|
||||||
* [Examples](#examples)
|
- [Examples](#examples)
|
||||||
* [value()](#value)
|
- [value()](#value)
|
||||||
* [Array-like Properties](#array-like-properties)
|
- [Array-like Properties](#array-like-properties)
|
||||||
* [Channel](#channel)
|
- [Channel](#channel)
|
||||||
* [Channel([bufferLength]) -> Channel](#channelbufferlength---channel)
|
- [Channel([bufferLength]) -> Channel](#channelbufferlength-channel)
|
||||||
* [Channel.isChannel(value) -> Boolean](#channelischannelvalue---boolean)
|
- [Channel.isChannel(value) -> Boolean](#channelischannelvalue-boolean)
|
||||||
* [Channel.of(...values) -> read-only Channel](#channelofvalues---read-only-channel)
|
- [Channel.of(...values) -> read-only Channel](#channelofvalues-read-only-channel)
|
||||||
* [Channel.from(callback | iterable | stream.Readable[, mapfn [, thisArg]]) -> read-only Channel](#channelfromcallback--iterable--streamreadable-mapfn--thisarg---read-only-channel)
|
- [Channel.from(callback | iterable | stream.Readable[, mapfn [, thisArg]]) -> read-only Channel](#channelfromcallback-iterable-streamreadable-mapfn-thisarg-read-only-channel)
|
||||||
* [Examples](#examples-1)
|
- [Examples](#examples-1)
|
||||||
* [Channel Object](#channel-object)
|
- [Channel Object](#channel-object)
|
||||||
* [concat(...arguments) -> Channel](#concatarguments---channel)
|
- [concat(...arguments) -> Channel](#concatarguments-channel)
|
||||||
* [every(callbackfn[, thisArg]) -> (async) Boolean](#everycallbackfn-thisarg---async-boolean)
|
- [every(callbackfn[, thisArg]) -> (async) Boolean](#everycallbackfn-thisarg-async-boolean)
|
||||||
* [filter(callbackfn[, thisArg]) -> Channel](#filtercallbackfn-thisarg---channel)
|
- [filter(callbackfn[, thisArg]) -> Channel](#filtercallbackfn-thisarg-channel)
|
||||||
* [forEach(callbackfn[, thisArg]) -> (async)](#foreachcallbackfn-thisarg---async)
|
- [flat([depth = 1]) -> Channel](#flatdepth-1-channel)
|
||||||
* [join(separator) -> (async) String](#joinseparator---async-string)
|
- [flatMap (mapperFunction[, thisArg]) -> Channel](#flatmap-mapperfunction-thisarg-channel)
|
||||||
* [length](#length)
|
- [forEach(callbackfn[, thisArg]) -> (async)](#foreachcallbackfn-thisarg-async)
|
||||||
* [map(callbackfn[, thisArg]) -> Channel](#mapcallbackfn-thisarg---channel)
|
- [join(separator) -> (async) String](#joinseparator-async-string)
|
||||||
* [push(value) -> (async) bufferLength](#pushvalue---async-bufferlength)
|
- [length](#length)
|
||||||
* [reduce(callbackfn[, initialValue]) -> (async)](#reducecallbackfn-initialvalue---async)
|
- [map(mapperFunction[, thisArg]) -> Channel](#mapmapperfunction-thisarg-channel)
|
||||||
* [shift() -> (async)](#shift---async)
|
- [push(value) -> (async) bufferLength](#pushvalue-async-bufferlength)
|
||||||
* [slice(start[, end]) -> Channel](#slicestart-end---channel)
|
- [reduce(callbackfn[, initialValue]) -> (async)](#reducecallbackfn-initialvalue-async)
|
||||||
* [some(callbackfn[, thisArg])](#somecallbackfn-thisarg)
|
- [shift() -> (async)](#shift-async)
|
||||||
* [toString() -> String](#tostring---string)
|
- [slice(start[, end]) -> Channel](#slicestart-end-channel)
|
||||||
* [values() -> (async) iterator](#values---async-iterator)
|
- [some(callbackfn[, thisArg])](#somecallbackfn-thisarg)
|
||||||
* [Functional API](#functional-api)
|
- [toString() -> String](#tostring-string)
|
||||||
|
- [values() -> (async) iterator](#values-async-iterator)
|
||||||
|
- [Functional API](#functional-api)
|
||||||
|
|
||||||
<!-- /TOC -->
|
<!-- /TOC -->
|
||||||
|
|
||||||
|
@ -239,6 +241,23 @@ instead.
|
||||||
Unlike in the Array version of `filter`, `callbackfn` is called with only one
|
Unlike in the Array version of `filter`, `callbackfn` is called with only one
|
||||||
argument.
|
argument.
|
||||||
|
|
||||||
|
### flat([depth = 1]) -> Channel
|
||||||
|
|
||||||
|
Create a new channel with values from the existing channel. If any of the
|
||||||
|
values are themselves channels, flatten them by pushing their values into the
|
||||||
|
new channel instead (while repeating this behavior up to `depth` times).
|
||||||
|
|
||||||
|
### flatMap (mapperFunction[, thisArg]) -> Channel
|
||||||
|
|
||||||
|
Call `mapperFunction` once for each value in the channel and flatten the result
|
||||||
|
(with depth 1).
|
||||||
|
|
||||||
|
If `thisArg` is provided it will be used as the `this` value for each invocation
|
||||||
|
of `mapperFunction`. If it is not provided, `undefined` is used instead.
|
||||||
|
|
||||||
|
Unlike in `Array`'s `flatMap` method, `mapperFunction` is called with only one
|
||||||
|
argument.
|
||||||
|
|
||||||
### forEach(callbackfn[, thisArg]) -> (async)
|
### forEach(callbackfn[, thisArg]) -> (async)
|
||||||
|
|
||||||
The promise returned by `forEach` resolves when the channel is closed:
|
The promise returned by `forEach` resolves when the channel is closed:
|
||||||
|
@ -275,25 +294,24 @@ provided, a single comma is used as the separator.
|
||||||
|
|
||||||
The length of the channel's buffer.
|
The length of the channel's buffer.
|
||||||
|
|
||||||
### map(callbackfn[, thisArg]) -> Channel
|
### map(mapperFunction[, thisArg]) -> Channel
|
||||||
|
|
||||||
`callbackfn` should be a function that accepts one argument. `map` calls
|
Call `mapperFunction` once for each value in the channel and construct a new
|
||||||
`callbackfn` once for each value in the channel and constructs a new Channel
|
channel with the results.
|
||||||
from the results.
|
|
||||||
|
|
||||||
If a `thisArg` parameter is provided, it will be used as the `this` value for
|
If `thisArg` is provided it will be used as the `this` value for each invocation
|
||||||
each invocation of `callbackfn`. If it is not provided, `undefined` is used
|
of `mapperFunction`. If it is not provided, `undefined` is used instead.
|
||||||
instead.
|
|
||||||
|
|
||||||
Unlike `Array`'s method, `callbackfn` is called with only one argument.
|
Unlike in `Array`'s `map` method, `mapperFunction` is called with only one
|
||||||
|
argument.
|
||||||
|
|
||||||
### push(value) -> (async) bufferLength
|
### push(value) -> (async) bufferLength
|
||||||
|
|
||||||
Send the value into the channel and return a promise that resolves when the
|
Send the value into the channel and return a promise that resolves when the
|
||||||
value has been shifted or placed in the buffer.
|
value has been shifted or placed in the buffer.
|
||||||
|
|
||||||
* Throw a `TypeError` when attempting to push to a closed channel.
|
- Throw a `TypeError` when attempting to push to a closed channel.
|
||||||
* Throw a `TypeError` when attempting to push `undefined` because it's a
|
- Throw a `TypeError` when attempting to push `undefined` because it's a
|
||||||
reserved value used to indicate a closed channel.
|
reserved value used to indicate a closed channel.
|
||||||
|
|
||||||
The push can be cancelled before completion by calling `cancel` on the returned
|
The push can be cancelled before completion by calling `cancel` on the returned
|
||||||
|
|
22
lib/index.js
22
lib/index.js
|
@ -172,6 +172,28 @@ const Channel = function(length = 0) {
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
flat: depth => {
|
||||||
|
const output = Channel();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await readOnly.forEach(async value => {
|
||||||
|
if (Channel.isChannel(value)) {
|
||||||
|
const input = depth > 1 ? value.flat(depth - 1) : value;
|
||||||
|
await input.forEach(output.push);
|
||||||
|
} else {
|
||||||
|
await output.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await output.close();
|
||||||
|
})();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
},
|
||||||
|
|
||||||
|
flatMap: (mapperFunction, thisArg) =>
|
||||||
|
readOnly.map(mapperFunction, thisArg).flat(),
|
||||||
|
|
||||||
forEach: async (callbackfn, thisArg) => {
|
forEach: async (callbackfn, thisArg) => {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const value = await readOnly.shift();
|
const value = await readOnly.shift();
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"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 {
|
||||||
|
@ -250,6 +250,37 @@ describe(`Channel object`, function() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`flat`, async function() {
|
||||||
|
const flat1 = Channel.of(1, 2, Channel.of(3, 4)).flat();
|
||||||
|
assert.deepEqual(await flat1.values(), [1, 2, 3, 4]);
|
||||||
|
|
||||||
|
const flat2 = Channel.of(1, 2, Channel.of(3, 4, Channel.of(5, 6))).flat();
|
||||||
|
assert.equal(await flat2.shift(), 1);
|
||||||
|
assert.equal(await flat2.shift(), 2);
|
||||||
|
assert.equal(await flat2.shift(), 3);
|
||||||
|
assert.equal(await flat2.shift(), 4);
|
||||||
|
assert.deepEqual(await (await flat2.shift()).values(), [5, 6]);
|
||||||
|
|
||||||
|
const flat3 = Channel.of(1, 2, Channel.of(3, 4, Channel.of(5, 6))).flat(2);
|
||||||
|
assert.deepEqual(await flat3.values(), [1, 2, 3, 4, 5, 6]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`flatMap`, async function() {
|
||||||
|
assert.deepEqual(
|
||||||
|
await Channel.of(1, 2, 3, 4)
|
||||||
|
.flatMap(x => Channel.of(x * 2))
|
||||||
|
.values(),
|
||||||
|
[2, 4, 6, 8]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
await Channel.of(`it's Sunny in`, ``, `California`)
|
||||||
|
.flatMap(x => Channel.from(x.split(` `)))
|
||||||
|
.values(),
|
||||||
|
[`it's`, `Sunny`, `in`, ``, `California`]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it(`forEach`, async function() {
|
it(`forEach`, async function() {
|
||||||
const output = [];
|
const output = [];
|
||||||
await Channel.of(0, 1, 2).forEach(value => output.push(value));
|
await Channel.of(0, 1, 2).forEach(value => output.push(value));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue