├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── bundle │ ├── for-await.js │ ├── index.js │ ├── index.mjs │ └── module.js └── declarations │ └── index.d.ts ├── examples ├── browser │ ├── browser-adapter.js │ └── fromFetch.js ├── csv-parser.js ├── fixture.csv ├── index.html ├── node.js ├── node │ └── fromFile.js ├── readme.md └── scripts │ ├── fixtures.js │ └── rollup.node.js ├── package-lock.json ├── package.json ├── rollup ├── build.js └── test.js ├── src ├── index.d.ts ├── index.js └── lib │ ├── operators.js │ ├── stream.js │ └── util.js └── test ├── concat.js ├── every.js ├── filter.js ├── find.js ├── flatMap.js ├── includes.js ├── index.js ├── map.js ├── reduce.js ├── skip.js ├── slice.js ├── some.js ├── stream.js ├── take.js ├── util └── source.js └── utils.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:9 11 | 12 | steps: 13 | - checkout 14 | 15 | # Download and cache dependencies 16 | - restore_cache: 17 | keys: 18 | - v1-dependencies-{{ checksum "package.json" }} 19 | # fallback to using the latest cache if no exact match is found 20 | - v1-dependencies- 21 | 22 | - run: npm install 23 | 24 | - save_cache: 25 | paths: 26 | - node_modules 27 | key: v1-dependencies-{{ checksum "package.json" }} 28 | 29 | # run tests! 30 | - run: npm run test:ci 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea 61 | 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 RENARD Laurent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/lorenzofox3/for-await.svg?style=svg)](https://circleci.com/gh/lorenzofox3/for-await) 2 | 3 | # for-await 4 | 5 | Operators and stream semantic for asyncIterators. 6 | 7 | ``npm install @lorenzofox3/for-await`` 8 | 9 | ## Motivation 10 | 11 | [AsyncIterator](https://tc39.github.io/proposal-async-iteration/#sec-asynciterator-interface) and [AsyncGenerator](https://tc39.github.io/proposal-async-iteration/#sec-asyncgenerator-objects) (and the `for await` statement to consume them) have been integrated into es2018 specification and have started to land in different Javascript engines: 12 | 13 | 1. Nodejs > v9 (with harmony flag for some versions) 14 | 2. Chrome 15 | 3. Firefox 16 | 17 | They provide a new way to create data structures which have the semantic of readable streams: ie that produce values in time (asynchronously) 18 | 19 | ```Javascript 20 | const wait = (time = 100) => new Promise(resolve => { 21 | setTimeout(() => resolve(),time); 22 | }); 23 | 24 | // produce a sequence of integer every 100ms 25 | const counter = async function * (limit = 5){ 26 | let i = 0; 27 | while(true){ 28 | if(i >= limit){ 29 | break; 30 | } 31 | await wait(); 32 | yield i; 33 | i++; 34 | } 35 | } 36 | 37 | // consume it 38 | for await (const i of counter()){ 39 | console.log(i); 40 | } 41 | 42 | // > 0 (after 100ms) 43 | // > 1 (after 200ms) 44 | // .. 45 | // > 4 (after 500ms) 46 | ``` 47 | 48 | And this is very powerful ! 49 | 50 | This library aims at providing operators and data structures with almost the same API than regular synchronous collections (like Array) implement, so you can manipulate 51 | these streams as if they were synchronous collections. 52 | 53 | ```Javascript 54 | import {stream} from 'for-await'; 55 | 56 | const oddSquaredCounter = stream(counter()) 57 | .filter(i => i % 2 === 0) // keep odd 58 | .map(i => i * i) // square it 59 | 60 | // and consume it 61 | 62 | for await (const v of oddSquaredCounter){ 63 | console.log(v); 64 | } 65 | 66 | // > 0 (after 100 ms) 67 | // > 4 (after 300 ms) 68 | // > 16 (after 500 ms) 69 | ``` 70 | 71 | This could sound familiar to anyone who has already tried [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming) or have used the same kind of abstractions on streams provided by some nodejs libraries (like [through](https://www.npmjs.com/package/through2)). 72 | 73 | However this implementation relies only on native EcmaScript features which makes it very lightweight and easier to use on different platforms. 74 | 75 | You will only have to implement an adapter (I will likely write modules for common data source in Nodejs and in the Browser) so your data source implements the standard asyncIterator interface (and this has become way more easier with async generators). 76 | 77 | [See our csv parser for nodejs and browser]('./examples'). 78 | 79 | ## Operators 80 | 81 | ### semantic 82 | If you are not familiar with the synchronous iterable/iterator protocol, I strongly recommend the [Reginald Braithwaite book](https://leanpub.com/javascriptallongesix) and [his essays](http://raganwald.com/2017/07/22/closing-iterables-is-a-leaky-abstraction.html) 83 | 84 | Note all the operators which do not return a scalar value follow the same semantic than the underlying source: 85 | 1. If source streams are consumable once the resulting iterable is consumable once 86 | 2. If source streams implements control flow hooks (like return or throw), these hooks will be called as well so your data source is properly released etc 87 | 88 | For example in a synchronous world you already know that 89 | ```Javascript 90 | const array = [0,1,2] // implements Iterable protocol 91 | 92 | for (const i of array){ 93 | console.log(i); 94 | } 95 | 96 | for (const i of array){ 97 | console.log(i); 98 | } 99 | 100 | // > 0 101 | // > 1 102 | // > 2 103 | // > 0 104 | // > 1 105 | // > 2 106 | 107 | // However 108 | 109 | const counterGen = function * (){ 110 | yield 0; 111 | yield 1; 112 | yield 3; 113 | } 114 | 115 | const oneTwoThree = counterGen(); 116 | 117 | for (const i of onTwoThree){ 118 | console.log(i); 119 | } 120 | 121 | for (const i of onTwoTree){ 122 | console.log(i); 123 | } 124 | 125 | // > 0 126 | // > 1 127 | // > 2 128 | // And nothing as the generator is already exhausted 129 | ``` 130 | 131 | The will be the same for our async iterators 132 | ```Javascript 133 | import {map} from 'for-await'; 134 | 135 | // Assuming the asynchronous counter of the introduction 136 | const zeroOneTwoSquared = map(x=>x*x, counter(3)); 137 | 138 | for await (const i of zeroOneTwoSquared){ 139 | console.log(i); 140 | } 141 | 142 | for await (const i of zeroOneTwoSquared){ 143 | console.log(i); 144 | } 145 | 146 | // > 0 (100ms) 147 | // > 1 (200ms) 148 | // > 4 (300ms) 149 | // And nothing as the generator is already exhausted 150 | 151 | //However 152 | 153 | const iteratable = { 154 | [Symbol.asyncIterator]:counter 155 | } 156 | 157 | for await (const i of iterable){ 158 | console.log(i); 159 | } 160 | 161 | for await (const i of iterable){ 162 | console.log(i); 163 | } 164 | 165 | // > 0 (100ms) 166 | // > 1 (200ms) 167 | // > 4 (300ms) 168 | // > 9 (400ms) 169 | // > 16 (500ms) 170 | // > 0 (600ms) 171 | // > 1 (700ms) 172 | // > 4 (800ms) 173 | // > 9 (900ms) 174 | // > 16 (1000ms) 175 | ``` 176 | 177 | Most of the operators are curried: 178 | 179 | ```Javascript 180 | import {map} from 'for-await'; 181 | 182 | const square=map(x=>x*x); 183 | 184 | for await (const i of square(counter()){ 185 | // do something 186 | } 187 | 188 | //and another one 189 | for await (const i of square(counter()){ 190 | // do something 191 | } 192 | ``` 193 | 194 | ### operators list 195 | 196 | #### return a new Async Iterable 197 | 1. map 198 | 2. filter 199 | 3. slice 200 | 4. flatMap (flatten stream of streams) 201 | 5. concat 202 | 203 | #### Return a scalar (as a promise) 204 | 1. reduce 205 | 2. find 206 | 3. findIndex 207 | 4. includes 208 | 5. every 209 | 6. some 210 | 211 | ## Data structure 212 | 213 | Even more convenient you can use the stream data structure which gives you almost the same API than Arrays. 214 | 1. An instance of a stream will implement all the operators above 215 | 2. Every method which returns an async Iterable will actually return a new instance of stream (so you can chain them together) 216 | -------------------------------------------------------------------------------- /dist/bundle/for-await.js: -------------------------------------------------------------------------------- 1 | var ForAwait = (function (exports) { 2 | 'use strict'; 3 | 4 | // with two arguments 5 | const curry = (fn) => (a, b) => b === void 0 ? b => fn(a, b) : fn(a, b); 6 | const toCurriedIterable = gen => curry((a, b) => ({ 7 | [Symbol.asyncIterator]() { 8 | return gen(a, b); 9 | } 10 | })); 11 | const toIterable = gen => (...args) => ({ 12 | [Symbol.asyncIterator]() { 13 | return gen(...args); 14 | } 15 | }); 16 | 17 | const map = toCurriedIterable(async function* (fn, asyncIterable) { 18 | let index = 0; 19 | for await (const i of asyncIterable) { 20 | yield fn(i, index, asyncIterable); 21 | index++; 22 | } 23 | }); 24 | 25 | const filter = toCurriedIterable(async function* (fn, asyncIterable) { 26 | let index = 0; 27 | for await (const i of asyncIterable) { 28 | if (fn(i, index, asyncIterable) === true) { 29 | yield i; 30 | } 31 | index++; 32 | } 33 | }); 34 | 35 | const take = toCurriedIterable(async function* (number, asyncIterable) { 36 | let count = 1; 37 | for await (const i of asyncIterable) { 38 | if (number !== undefined && count > number) { 39 | break; 40 | } 41 | yield i; 42 | count++; 43 | } 44 | }); 45 | 46 | const skip = toCurriedIterable(async function* (limit, asyncIterable) { 47 | let count = 0; 48 | for await (const i of asyncIterable) { 49 | if (count < limit) { 50 | count++; 51 | continue; 52 | } 53 | yield i; 54 | } 55 | }); 56 | 57 | const flatMap = toCurriedIterable(async function* (fn, asyncIterable) { 58 | for await (const i of asyncIterable) { 59 | if (i[Symbol.asyncIterator]) { 60 | yield* map(fn, i); 61 | } else { 62 | yield fn(i); 63 | } 64 | } 65 | }); 66 | 67 | const acutalSlice = toIterable(async function* (s, e, iterable) { 68 | const toSkip = skip(s); 69 | const toTake = take(e !== void 0 ? e - s : e); 70 | for await (const i of toTake(toSkip(iterable))) { 71 | yield i; 72 | } 73 | }); 74 | const slice = (start, end, asyncIterable) => { 75 | let s = start || 0; 76 | let e = end; 77 | let iterable = asyncIterable; 78 | if (start && start[Symbol.asyncIterator] !== void 0) { 79 | iterable = start; 80 | s = 0; 81 | e = void 0; 82 | } else if (end && end[Symbol.asyncIterator] !== void 0) { 83 | iterable = end; 84 | s = start; 85 | e = void 0; 86 | } else if (asyncIterable === void 0) { 87 | return iterable => acutalSlice(s, e, iterable); 88 | } 89 | return acutalSlice(s, e, iterable); 90 | }; 91 | 92 | const concat = toIterable(async function* (...values) { 93 | for (const i of values) { 94 | if (i[Symbol.asyncIterator]) { 95 | yield* i; 96 | } else { 97 | yield i; 98 | } 99 | } 100 | }); 101 | 102 | const actualReduce = async (fn, initialValue, asyncIterable) => { 103 | let index = -1; 104 | const iterator = asyncIterable[Symbol.asyncIterator](); 105 | const next = async () => { 106 | index++; 107 | return iterator.next(); 108 | }; 109 | let acc = initialValue; 110 | 111 | if (initialValue === void 0) { 112 | acc = (await next()).value; 113 | } 114 | 115 | while (true) { 116 | const {value, done} = await next(); 117 | if (done === true) { 118 | return acc; 119 | } 120 | acc = fn(acc, value, index, asyncIterable); 121 | } 122 | }; 123 | const reduce = (fn, initVal, asyncIterable) => { 124 | let acc = initVal; 125 | let iterable = asyncIterable; 126 | 127 | if (initVal && initVal[Symbol.asyncIterator] !== void 0) { 128 | iterable = initVal; 129 | acc = void 0; 130 | } 131 | 132 | if (iterable === void 0) { 133 | return iterable => actualReduce(fn, acc, iterable); 134 | } 135 | 136 | return actualReduce(fn, acc, iterable); 137 | }; 138 | 139 | const findTuple = async (fn, asyncIterable) => { 140 | let index = 0; 141 | for await (const i of asyncIterable) { 142 | if (fn(i, index, asyncIterable)) { 143 | return {value: i, index: index}; 144 | } 145 | index++; 146 | } 147 | return {value: void 0, index: -1}; 148 | }; 149 | 150 | const find = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).value); 151 | 152 | const findIndex = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).index); 153 | 154 | const actualIncludes = async (item, from, iterable) => { 155 | const strictEqualToItem = findIndex(x => x === item); 156 | return (await strictEqualToItem(skip(from, iterable))) > -1; 157 | }; 158 | const includes = (item, from, asyncIterable) => { 159 | let start = from; 160 | let iterable = asyncIterable; 161 | 162 | if (from && from[Symbol.asyncIterator] !== void 0) { 163 | start = 0; 164 | iterable = from; 165 | } 166 | 167 | if (iterable === void 0) { 168 | return iterable => actualIncludes(item, start, iterable); 169 | } 170 | 171 | return actualIncludes(item, start, iterable); 172 | }; 173 | 174 | const every = curry(async (fn, asyncIterable) => { 175 | let index = 0; 176 | for await(const i of asyncIterable) { 177 | if (!fn(i, index, asyncIterable)) { 178 | return false; 179 | } 180 | index++; 181 | } 182 | return true; 183 | }); 184 | 185 | const some = curry(async (fn, asyncIterable) => { 186 | let index = 0; 187 | for await(const i of asyncIterable) { 188 | if (fn(i, index, asyncIterable)) { 189 | return true; 190 | } 191 | index++; 192 | } 193 | return false; 194 | }); 195 | 196 | /* 197 | The iterable won't always be consumed with a for await statement (which implicitly convert an iterable into a asyncIterable) so we need to explicitly make it async iterable 198 | for await (const t of [1,2,3,4,5]){ 199 | //no problem 200 | } 201 | 202 | but 203 | 204 | const iterator = [1,2,3][Symbol.asyncIterator](); 205 | //problem 206 | */ 207 | const toAsync = toIterable(async function* (iterable) { 208 | yield* iterable; 209 | }); 210 | 211 | const proto = { 212 | [Symbol.asyncIterator]() { 213 | return this._source[Symbol.asyncIterator](); 214 | }, 215 | map(fn) { 216 | return stream(map(fn, this)); 217 | }, 218 | filter(fn) { 219 | return stream(filter(fn, this)); 220 | }, 221 | flatMap(fn) { 222 | return stream(flatMap(fn, this)); 223 | }, 224 | slice(start = 0, end = void 0) { 225 | return stream(slice(start, end, this)); 226 | }, 227 | concat(...values) { 228 | return stream(concat(this, ...values)); 229 | }, 230 | reduce(fn, initialValue) { 231 | return reduce(fn, initialValue, this); 232 | }, 233 | find(fn) { 234 | return find(fn, this); 235 | }, 236 | findIndex(fn) { 237 | return findIndex(fn, this); 238 | }, 239 | includes(item, from = 0) { 240 | return includes(item, from, this); 241 | }, 242 | every(fn) { 243 | return every(fn, this); 244 | }, 245 | some(fn) { 246 | return some(fn, this); 247 | } 248 | }; 249 | 250 | const stream = iterable => { 251 | const source = !iterable[Symbol.asyncIterator] ? toAsync(iterable) : iterable; // we make a difference as any wrap of iterable has performance impact (for the moment) 252 | return Object.create(proto, {_source: {value: source}}); 253 | }; 254 | 255 | exports.concat = concat; 256 | exports.every = every; 257 | exports.filter = filter; 258 | exports.find = find; 259 | exports.findIndex = findIndex; 260 | exports.flatMap = flatMap; 261 | exports.from = toAsync; 262 | exports.includes = includes; 263 | exports.map = map; 264 | exports.reduce = reduce; 265 | exports.skip = skip; 266 | exports.slice = slice; 267 | exports.some = some; 268 | exports.stream = stream; 269 | exports.take = take; 270 | 271 | return exports; 272 | 273 | }({})); 274 | -------------------------------------------------------------------------------- /dist/bundle/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | // with two arguments 6 | const curry = (fn) => (a, b) => b === void 0 ? b => fn(a, b) : fn(a, b); 7 | const toCurriedIterable = gen => curry((a, b) => ({ 8 | [Symbol.asyncIterator]() { 9 | return gen(a, b); 10 | } 11 | })); 12 | const toIterable = gen => (...args) => ({ 13 | [Symbol.asyncIterator]() { 14 | return gen(...args); 15 | } 16 | }); 17 | 18 | const map = toCurriedIterable(async function* (fn, asyncIterable) { 19 | let index = 0; 20 | for await (const i of asyncIterable) { 21 | yield fn(i, index, asyncIterable); 22 | index++; 23 | } 24 | }); 25 | 26 | const filter = toCurriedIterable(async function* (fn, asyncIterable) { 27 | let index = 0; 28 | for await (const i of asyncIterable) { 29 | if (fn(i, index, asyncIterable) === true) { 30 | yield i; 31 | } 32 | index++; 33 | } 34 | }); 35 | 36 | const take = toCurriedIterable(async function* (number, asyncIterable) { 37 | let count = 1; 38 | for await (const i of asyncIterable) { 39 | if (number !== undefined && count > number) { 40 | break; 41 | } 42 | yield i; 43 | count++; 44 | } 45 | }); 46 | 47 | const skip = toCurriedIterable(async function* (limit, asyncIterable) { 48 | let count = 0; 49 | for await (const i of asyncIterable) { 50 | if (count < limit) { 51 | count++; 52 | continue; 53 | } 54 | yield i; 55 | } 56 | }); 57 | 58 | const flatMap = toCurriedIterable(async function* (fn, asyncIterable) { 59 | for await (const i of asyncIterable) { 60 | if (i[Symbol.asyncIterator]) { 61 | yield* map(fn, i); 62 | } else { 63 | yield fn(i); 64 | } 65 | } 66 | }); 67 | 68 | const acutalSlice = toIterable(async function* (s, e, iterable) { 69 | const toSkip = skip(s); 70 | const toTake = take(e !== void 0 ? e - s : e); 71 | for await (const i of toTake(toSkip(iterable))) { 72 | yield i; 73 | } 74 | }); 75 | const slice = (start, end, asyncIterable) => { 76 | let s = start || 0; 77 | let e = end; 78 | let iterable = asyncIterable; 79 | if (start && start[Symbol.asyncIterator] !== void 0) { 80 | iterable = start; 81 | s = 0; 82 | e = void 0; 83 | } else if (end && end[Symbol.asyncIterator] !== void 0) { 84 | iterable = end; 85 | s = start; 86 | e = void 0; 87 | } else if (asyncIterable === void 0) { 88 | return iterable => acutalSlice(s, e, iterable); 89 | } 90 | return acutalSlice(s, e, iterable); 91 | }; 92 | 93 | const concat = toIterable(async function* (...values) { 94 | for (const i of values) { 95 | if (i[Symbol.asyncIterator]) { 96 | yield* i; 97 | } else { 98 | yield i; 99 | } 100 | } 101 | }); 102 | 103 | const actualReduce = async (fn, initialValue, asyncIterable) => { 104 | let index = -1; 105 | const iterator = asyncIterable[Symbol.asyncIterator](); 106 | const next = async () => { 107 | index++; 108 | return iterator.next(); 109 | }; 110 | let acc = initialValue; 111 | 112 | if (initialValue === void 0) { 113 | acc = (await next()).value; 114 | } 115 | 116 | while (true) { 117 | const {value, done} = await next(); 118 | if (done === true) { 119 | return acc; 120 | } 121 | acc = fn(acc, value, index, asyncIterable); 122 | } 123 | }; 124 | const reduce = (fn, initVal, asyncIterable) => { 125 | let acc = initVal; 126 | let iterable = asyncIterable; 127 | 128 | if (initVal && initVal[Symbol.asyncIterator] !== void 0) { 129 | iterable = initVal; 130 | acc = void 0; 131 | } 132 | 133 | if (iterable === void 0) { 134 | return iterable => actualReduce(fn, acc, iterable); 135 | } 136 | 137 | return actualReduce(fn, acc, iterable); 138 | }; 139 | 140 | const findTuple = async (fn, asyncIterable) => { 141 | let index = 0; 142 | for await (const i of asyncIterable) { 143 | if (fn(i, index, asyncIterable)) { 144 | return {value: i, index: index}; 145 | } 146 | index++; 147 | } 148 | return {value: void 0, index: -1}; 149 | }; 150 | 151 | const find = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).value); 152 | 153 | const findIndex = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).index); 154 | 155 | const actualIncludes = async (item, from, iterable) => { 156 | const strictEqualToItem = findIndex(x => x === item); 157 | return (await strictEqualToItem(skip(from, iterable))) > -1; 158 | }; 159 | const includes = (item, from, asyncIterable) => { 160 | let start = from; 161 | let iterable = asyncIterable; 162 | 163 | if (from && from[Symbol.asyncIterator] !== void 0) { 164 | start = 0; 165 | iterable = from; 166 | } 167 | 168 | if (iterable === void 0) { 169 | return iterable => actualIncludes(item, start, iterable); 170 | } 171 | 172 | return actualIncludes(item, start, iterable); 173 | }; 174 | 175 | const every = curry(async (fn, asyncIterable) => { 176 | let index = 0; 177 | for await(const i of asyncIterable) { 178 | if (!fn(i, index, asyncIterable)) { 179 | return false; 180 | } 181 | index++; 182 | } 183 | return true; 184 | }); 185 | 186 | const some = curry(async (fn, asyncIterable) => { 187 | let index = 0; 188 | for await(const i of asyncIterable) { 189 | if (fn(i, index, asyncIterable)) { 190 | return true; 191 | } 192 | index++; 193 | } 194 | return false; 195 | }); 196 | 197 | /* 198 | The iterable won't always be consumed with a for await statement (which implicitly convert an iterable into a asyncIterable) so we need to explicitly make it async iterable 199 | for await (const t of [1,2,3,4,5]){ 200 | //no problem 201 | } 202 | 203 | but 204 | 205 | const iterator = [1,2,3][Symbol.asyncIterator](); 206 | //problem 207 | */ 208 | const toAsync = toIterable(async function* (iterable) { 209 | yield* iterable; 210 | }); 211 | 212 | const proto = { 213 | [Symbol.asyncIterator]() { 214 | return this._source[Symbol.asyncIterator](); 215 | }, 216 | map(fn) { 217 | return stream(map(fn, this)); 218 | }, 219 | filter(fn) { 220 | return stream(filter(fn, this)); 221 | }, 222 | flatMap(fn) { 223 | return stream(flatMap(fn, this)); 224 | }, 225 | slice(start = 0, end = void 0) { 226 | return stream(slice(start, end, this)); 227 | }, 228 | concat(...values) { 229 | return stream(concat(this, ...values)); 230 | }, 231 | reduce(fn, initialValue) { 232 | return reduce(fn, initialValue, this); 233 | }, 234 | find(fn) { 235 | return find(fn, this); 236 | }, 237 | findIndex(fn) { 238 | return findIndex(fn, this); 239 | }, 240 | includes(item, from = 0) { 241 | return includes(item, from, this); 242 | }, 243 | every(fn) { 244 | return every(fn, this); 245 | }, 246 | some(fn) { 247 | return some(fn, this); 248 | } 249 | }; 250 | 251 | const stream = iterable => { 252 | const source = !iterable[Symbol.asyncIterator] ? toAsync(iterable) : iterable; // we make a difference as any wrap of iterable has performance impact (for the moment) 253 | return Object.create(proto, {_source: {value: source}}); 254 | }; 255 | 256 | exports.concat = concat; 257 | exports.every = every; 258 | exports.filter = filter; 259 | exports.find = find; 260 | exports.findIndex = findIndex; 261 | exports.flatMap = flatMap; 262 | exports.from = toAsync; 263 | exports.includes = includes; 264 | exports.map = map; 265 | exports.reduce = reduce; 266 | exports.skip = skip; 267 | exports.slice = slice; 268 | exports.some = some; 269 | exports.stream = stream; 270 | exports.take = take; 271 | -------------------------------------------------------------------------------- /dist/bundle/index.mjs: -------------------------------------------------------------------------------- 1 | // with two arguments 2 | const curry = (fn) => (a, b) => b === void 0 ? b => fn(a, b) : fn(a, b); 3 | const toCurriedIterable = gen => curry((a, b) => ({ 4 | [Symbol.asyncIterator]() { 5 | return gen(a, b); 6 | } 7 | })); 8 | const toIterable = gen => (...args) => ({ 9 | [Symbol.asyncIterator]() { 10 | return gen(...args); 11 | } 12 | }); 13 | 14 | const map = toCurriedIterable(async function* (fn, asyncIterable) { 15 | let index = 0; 16 | for await (const i of asyncIterable) { 17 | yield fn(i, index, asyncIterable); 18 | index++; 19 | } 20 | }); 21 | 22 | const filter = toCurriedIterable(async function* (fn, asyncIterable) { 23 | let index = 0; 24 | for await (const i of asyncIterable) { 25 | if (fn(i, index, asyncIterable) === true) { 26 | yield i; 27 | } 28 | index++; 29 | } 30 | }); 31 | 32 | const take = toCurriedIterable(async function* (number, asyncIterable) { 33 | let count = 1; 34 | for await (const i of asyncIterable) { 35 | if (number !== undefined && count > number) { 36 | break; 37 | } 38 | yield i; 39 | count++; 40 | } 41 | }); 42 | 43 | const skip = toCurriedIterable(async function* (limit, asyncIterable) { 44 | let count = 0; 45 | for await (const i of asyncIterable) { 46 | if (count < limit) { 47 | count++; 48 | continue; 49 | } 50 | yield i; 51 | } 52 | }); 53 | 54 | const flatMap = toCurriedIterable(async function* (fn, asyncIterable) { 55 | for await (const i of asyncIterable) { 56 | if (i[Symbol.asyncIterator]) { 57 | yield* map(fn, i); 58 | } else { 59 | yield fn(i); 60 | } 61 | } 62 | }); 63 | 64 | const acutalSlice = toIterable(async function* (s, e, iterable) { 65 | const toSkip = skip(s); 66 | const toTake = take(e !== void 0 ? e - s : e); 67 | for await (const i of toTake(toSkip(iterable))) { 68 | yield i; 69 | } 70 | }); 71 | const slice = (start, end, asyncIterable) => { 72 | let s = start || 0; 73 | let e = end; 74 | let iterable = asyncIterable; 75 | if (start && start[Symbol.asyncIterator] !== void 0) { 76 | iterable = start; 77 | s = 0; 78 | e = void 0; 79 | } else if (end && end[Symbol.asyncIterator] !== void 0) { 80 | iterable = end; 81 | s = start; 82 | e = void 0; 83 | } else if (asyncIterable === void 0) { 84 | return iterable => acutalSlice(s, e, iterable); 85 | } 86 | return acutalSlice(s, e, iterable); 87 | }; 88 | 89 | const concat = toIterable(async function* (...values) { 90 | for (const i of values) { 91 | if (i[Symbol.asyncIterator]) { 92 | yield* i; 93 | } else { 94 | yield i; 95 | } 96 | } 97 | }); 98 | 99 | const actualReduce = async (fn, initialValue, asyncIterable) => { 100 | let index = -1; 101 | const iterator = asyncIterable[Symbol.asyncIterator](); 102 | const next = async () => { 103 | index++; 104 | return iterator.next(); 105 | }; 106 | let acc = initialValue; 107 | 108 | if (initialValue === void 0) { 109 | acc = (await next()).value; 110 | } 111 | 112 | while (true) { 113 | const {value, done} = await next(); 114 | if (done === true) { 115 | return acc; 116 | } 117 | acc = fn(acc, value, index, asyncIterable); 118 | } 119 | }; 120 | const reduce = (fn, initVal, asyncIterable) => { 121 | let acc = initVal; 122 | let iterable = asyncIterable; 123 | 124 | if (initVal && initVal[Symbol.asyncIterator] !== void 0) { 125 | iterable = initVal; 126 | acc = void 0; 127 | } 128 | 129 | if (iterable === void 0) { 130 | return iterable => actualReduce(fn, acc, iterable); 131 | } 132 | 133 | return actualReduce(fn, acc, iterable); 134 | }; 135 | 136 | const findTuple = async (fn, asyncIterable) => { 137 | let index = 0; 138 | for await (const i of asyncIterable) { 139 | if (fn(i, index, asyncIterable)) { 140 | return {value: i, index: index}; 141 | } 142 | index++; 143 | } 144 | return {value: void 0, index: -1}; 145 | }; 146 | 147 | const find = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).value); 148 | 149 | const findIndex = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).index); 150 | 151 | const actualIncludes = async (item, from, iterable) => { 152 | const strictEqualToItem = findIndex(x => x === item); 153 | return (await strictEqualToItem(skip(from, iterable))) > -1; 154 | }; 155 | const includes = (item, from, asyncIterable) => { 156 | let start = from; 157 | let iterable = asyncIterable; 158 | 159 | if (from && from[Symbol.asyncIterator] !== void 0) { 160 | start = 0; 161 | iterable = from; 162 | } 163 | 164 | if (iterable === void 0) { 165 | return iterable => actualIncludes(item, start, iterable); 166 | } 167 | 168 | return actualIncludes(item, start, iterable); 169 | }; 170 | 171 | const every = curry(async (fn, asyncIterable) => { 172 | let index = 0; 173 | for await(const i of asyncIterable) { 174 | if (!fn(i, index, asyncIterable)) { 175 | return false; 176 | } 177 | index++; 178 | } 179 | return true; 180 | }); 181 | 182 | const some = curry(async (fn, asyncIterable) => { 183 | let index = 0; 184 | for await(const i of asyncIterable) { 185 | if (fn(i, index, asyncIterable)) { 186 | return true; 187 | } 188 | index++; 189 | } 190 | return false; 191 | }); 192 | 193 | /* 194 | The iterable won't always be consumed with a for await statement (which implicitly convert an iterable into a asyncIterable) so we need to explicitly make it async iterable 195 | for await (const t of [1,2,3,4,5]){ 196 | //no problem 197 | } 198 | 199 | but 200 | 201 | const iterator = [1,2,3][Symbol.asyncIterator](); 202 | //problem 203 | */ 204 | const toAsync = toIterable(async function* (iterable) { 205 | yield* iterable; 206 | }); 207 | 208 | const proto = { 209 | [Symbol.asyncIterator]() { 210 | return this._source[Symbol.asyncIterator](); 211 | }, 212 | map(fn) { 213 | return stream(map(fn, this)); 214 | }, 215 | filter(fn) { 216 | return stream(filter(fn, this)); 217 | }, 218 | flatMap(fn) { 219 | return stream(flatMap(fn, this)); 220 | }, 221 | slice(start = 0, end = void 0) { 222 | return stream(slice(start, end, this)); 223 | }, 224 | concat(...values) { 225 | return stream(concat(this, ...values)); 226 | }, 227 | reduce(fn, initialValue) { 228 | return reduce(fn, initialValue, this); 229 | }, 230 | find(fn) { 231 | return find(fn, this); 232 | }, 233 | findIndex(fn) { 234 | return findIndex(fn, this); 235 | }, 236 | includes(item, from = 0) { 237 | return includes(item, from, this); 238 | }, 239 | every(fn) { 240 | return every(fn, this); 241 | }, 242 | some(fn) { 243 | return some(fn, this); 244 | } 245 | }; 246 | 247 | const stream = iterable => { 248 | const source = !iterable[Symbol.asyncIterator] ? toAsync(iterable) : iterable; // we make a difference as any wrap of iterable has performance impact (for the moment) 249 | return Object.create(proto, {_source: {value: source}}); 250 | }; 251 | 252 | export { concat, every, filter, find, findIndex, flatMap, toAsync as from, includes, map, reduce, skip, slice, some, stream, take }; 253 | -------------------------------------------------------------------------------- /dist/bundle/module.js: -------------------------------------------------------------------------------- 1 | // with two arguments 2 | const curry = (fn) => (a, b) => b === void 0 ? b => fn(a, b) : fn(a, b); 3 | const toCurriedIterable = gen => curry((a, b) => ({ 4 | [Symbol.asyncIterator]() { 5 | return gen(a, b); 6 | } 7 | })); 8 | const toIterable = gen => (...args) => ({ 9 | [Symbol.asyncIterator]() { 10 | return gen(...args); 11 | } 12 | }); 13 | 14 | const map = toCurriedIterable(async function* (fn, asyncIterable) { 15 | let index = 0; 16 | for await (const i of asyncIterable) { 17 | yield fn(i, index, asyncIterable); 18 | index++; 19 | } 20 | }); 21 | 22 | const filter = toCurriedIterable(async function* (fn, asyncIterable) { 23 | let index = 0; 24 | for await (const i of asyncIterable) { 25 | if (fn(i, index, asyncIterable) === true) { 26 | yield i; 27 | } 28 | index++; 29 | } 30 | }); 31 | 32 | const take = toCurriedIterable(async function* (number, asyncIterable) { 33 | let count = 1; 34 | for await (const i of asyncIterable) { 35 | if (number !== undefined && count > number) { 36 | break; 37 | } 38 | yield i; 39 | count++; 40 | } 41 | }); 42 | 43 | const skip = toCurriedIterable(async function* (limit, asyncIterable) { 44 | let count = 0; 45 | for await (const i of asyncIterable) { 46 | if (count < limit) { 47 | count++; 48 | continue; 49 | } 50 | yield i; 51 | } 52 | }); 53 | 54 | const flatMap = toCurriedIterable(async function* (fn, asyncIterable) { 55 | for await (const i of asyncIterable) { 56 | if (i[Symbol.asyncIterator]) { 57 | yield* map(fn, i); 58 | } else { 59 | yield fn(i); 60 | } 61 | } 62 | }); 63 | 64 | const acutalSlice = toIterable(async function* (s, e, iterable) { 65 | const toSkip = skip(s); 66 | const toTake = take(e !== void 0 ? e - s : e); 67 | for await (const i of toTake(toSkip(iterable))) { 68 | yield i; 69 | } 70 | }); 71 | const slice = (start, end, asyncIterable) => { 72 | let s = start || 0; 73 | let e = end; 74 | let iterable = asyncIterable; 75 | if (start && start[Symbol.asyncIterator] !== void 0) { 76 | iterable = start; 77 | s = 0; 78 | e = void 0; 79 | } else if (end && end[Symbol.asyncIterator] !== void 0) { 80 | iterable = end; 81 | s = start; 82 | e = void 0; 83 | } else if (asyncIterable === void 0) { 84 | return iterable => acutalSlice(s, e, iterable); 85 | } 86 | return acutalSlice(s, e, iterable); 87 | }; 88 | 89 | const concat = toIterable(async function* (...values) { 90 | for (const i of values) { 91 | if (i[Symbol.asyncIterator]) { 92 | yield* i; 93 | } else { 94 | yield i; 95 | } 96 | } 97 | }); 98 | 99 | const actualReduce = async (fn, initialValue, asyncIterable) => { 100 | let index = -1; 101 | const iterator = asyncIterable[Symbol.asyncIterator](); 102 | const next = async () => { 103 | index++; 104 | return iterator.next(); 105 | }; 106 | let acc = initialValue; 107 | 108 | if (initialValue === void 0) { 109 | acc = (await next()).value; 110 | } 111 | 112 | while (true) { 113 | const {value, done} = await next(); 114 | if (done === true) { 115 | return acc; 116 | } 117 | acc = fn(acc, value, index, asyncIterable); 118 | } 119 | }; 120 | const reduce = (fn, initVal, asyncIterable) => { 121 | let acc = initVal; 122 | let iterable = asyncIterable; 123 | 124 | if (initVal && initVal[Symbol.asyncIterator] !== void 0) { 125 | iterable = initVal; 126 | acc = void 0; 127 | } 128 | 129 | if (iterable === void 0) { 130 | return iterable => actualReduce(fn, acc, iterable); 131 | } 132 | 133 | return actualReduce(fn, acc, iterable); 134 | }; 135 | 136 | const findTuple = async (fn, asyncIterable) => { 137 | let index = 0; 138 | for await (const i of asyncIterable) { 139 | if (fn(i, index, asyncIterable)) { 140 | return {value: i, index: index}; 141 | } 142 | index++; 143 | } 144 | return {value: void 0, index: -1}; 145 | }; 146 | 147 | const find = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).value); 148 | 149 | const findIndex = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).index); 150 | 151 | const actualIncludes = async (item, from, iterable) => { 152 | const strictEqualToItem = findIndex(x => x === item); 153 | return (await strictEqualToItem(skip(from, iterable))) > -1; 154 | }; 155 | const includes = (item, from, asyncIterable) => { 156 | let start = from; 157 | let iterable = asyncIterable; 158 | 159 | if (from && from[Symbol.asyncIterator] !== void 0) { 160 | start = 0; 161 | iterable = from; 162 | } 163 | 164 | if (iterable === void 0) { 165 | return iterable => actualIncludes(item, start, iterable); 166 | } 167 | 168 | return actualIncludes(item, start, iterable); 169 | }; 170 | 171 | const every = curry(async (fn, asyncIterable) => { 172 | let index = 0; 173 | for await(const i of asyncIterable) { 174 | if (!fn(i, index, asyncIterable)) { 175 | return false; 176 | } 177 | index++; 178 | } 179 | return true; 180 | }); 181 | 182 | const some = curry(async (fn, asyncIterable) => { 183 | let index = 0; 184 | for await(const i of asyncIterable) { 185 | if (fn(i, index, asyncIterable)) { 186 | return true; 187 | } 188 | index++; 189 | } 190 | return false; 191 | }); 192 | 193 | /* 194 | The iterable won't always be consumed with a for await statement (which implicitly convert an iterable into a asyncIterable) so we need to explicitly make it async iterable 195 | for await (const t of [1,2,3,4,5]){ 196 | //no problem 197 | } 198 | 199 | but 200 | 201 | const iterator = [1,2,3][Symbol.asyncIterator](); 202 | //problem 203 | */ 204 | const toAsync = toIterable(async function* (iterable) { 205 | yield* iterable; 206 | }); 207 | 208 | const proto = { 209 | [Symbol.asyncIterator]() { 210 | return this._source[Symbol.asyncIterator](); 211 | }, 212 | map(fn) { 213 | return stream(map(fn, this)); 214 | }, 215 | filter(fn) { 216 | return stream(filter(fn, this)); 217 | }, 218 | flatMap(fn) { 219 | return stream(flatMap(fn, this)); 220 | }, 221 | slice(start = 0, end = void 0) { 222 | return stream(slice(start, end, this)); 223 | }, 224 | concat(...values) { 225 | return stream(concat(this, ...values)); 226 | }, 227 | reduce(fn, initialValue) { 228 | return reduce(fn, initialValue, this); 229 | }, 230 | find(fn) { 231 | return find(fn, this); 232 | }, 233 | findIndex(fn) { 234 | return findIndex(fn, this); 235 | }, 236 | includes(item, from = 0) { 237 | return includes(item, from, this); 238 | }, 239 | every(fn) { 240 | return every(fn, this); 241 | }, 242 | some(fn) { 243 | return some(fn, this); 244 | } 245 | }; 246 | 247 | const stream = iterable => { 248 | const source = !iterable[Symbol.asyncIterator] ? toAsync(iterable) : iterable; // we make a difference as any wrap of iterable has performance impact (for the moment) 249 | return Object.create(proto, {_source: {value: source}}); 250 | }; 251 | 252 | export { concat, every, filter, find, findIndex, flatMap, toAsync as from, includes, map, reduce, skip, slice, some, stream, take }; 253 | -------------------------------------------------------------------------------- /dist/declarations/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface MapCallback { 4 | (item: T, index: number, iterable: AsyncIterable): U | Promise; 5 | } 6 | 7 | declare function map(callback: MapCallback): (iterable: AsyncIterable) => AsyncIterable; 8 | declare function map(callback: MapCallback, iterable: AsyncIterable): AsyncIterable; 9 | 10 | interface PredicateCallback { 11 | (item: T, index: number, iterable: AsyncIterable): boolean; 12 | } 13 | 14 | declare function filter(callback: PredicateCallback): (iterable: AsyncIterable) => AsyncIterable; 15 | declare function filter(callback: PredicateCallback, iterable: AsyncIterable): AsyncIterable; 16 | 17 | declare function take(number: number): (iterable: AsyncIterable) => AsyncIterable; 18 | declare function take(number: number, iterable: AsyncIterable): AsyncIterable; 19 | 20 | declare function skip(number: number): (iterable: AsyncIterable) => AsyncIterable; 21 | declare function skip(number: number, iterable: AsyncIterable): AsyncIterable; 22 | 23 | type ItemOrAsyncIterable = T | AsyncIterable; 24 | 25 | declare function flatMap(fn: MapCallback): (iterable: AsyncIterable>) => AsyncIterable; 26 | declare function flatMap(fn: MapCallback, iterable: AsyncIterable>): AsyncIterable; 27 | 28 | declare function slice(iterable: AsyncIterable): AsyncIterable; 29 | declare function slice(start: number, iterable: AsyncIterable): AsyncIterable; 30 | declare function slice(start: number, end: number, iterable: AsyncIterable): AsyncIterable; 31 | declare function slice(start?: number, end?: number): (iterable: AsyncIterable) => AsyncIterable; 32 | 33 | declare function concat(...args: ItemOrAsyncIterable[]): AsyncIterable; 34 | 35 | interface ReduceCallback { 36 | (accumulator: U, currentItem: T, index: number, iterable: AsyncIterable): Promise; 37 | } 38 | 39 | declare function reduce(fn: ReduceCallback, initialValue?: U): (iterable: AsyncIterable) => Promise; 40 | declare function reduce(fn: ReduceCallback, iterable: AsyncIterable): Promise; 41 | declare function reduce(fn: ReduceCallback, initialValue: U, iterable: AsyncIterable): Promise; 42 | 43 | interface TupleCallback { 44 | (item: T, index: number, iterable: AsyncIterable): boolean; 45 | } 46 | 47 | declare function find(fn: TupleCallback): (iterable: AsyncIterable) => Promise; 48 | declare function find(fn: TupleCallback, iterable: AsyncIterable): Promise; 49 | 50 | declare function findIndex(fn: TupleCallback): (iterable: AsyncIterable) => Promise; 51 | declare function findIndex(fn: TupleCallback, iterable: AsyncIterable): Promise; 52 | 53 | declare function includes(item: T, from ?: number): (iterable: AsyncIterable) => Promise; 54 | declare function includes(item: T, iterable: AsyncIterable): Promise; 55 | declare function includes(item: T, from: number, iterable: AsyncIterable): Promise; 56 | 57 | declare function every(fn: PredicateCallback, iterable: AsyncIterable): Promise; 58 | declare function every(fn: PredicateCallback): (iterable: AsyncIterable) => Promise; 59 | 60 | declare function some(fn: PredicateCallback, iterable: AsyncIterable): Promise; 61 | declare function some(fn: PredicateCallback): (iterable: AsyncIterable) => Promise; 62 | 63 | declare function from(iterable: Iterable): AsyncIterable; 64 | 65 | export interface Stream extends AsyncIterable { 66 | map(fn: MapCallback): Stream; 67 | 68 | filter(fn: PredicateCallback): Stream; 69 | 70 | flatMap(fn: MapCallback): Stream; 71 | 72 | slice(start?: number, end?: number): Stream; 73 | 74 | concat(...values: ItemOrAsyncIterable[]): Stream; 75 | 76 | reduce(fn: ReduceCallback, initialValue?: U): Promise; 77 | 78 | find(fn: TupleCallback): Promise; 79 | 80 | findIndex(fn: TupleCallback): Promise; 81 | 82 | includes(item: T, from?: number): Promise; 83 | 84 | every(fn: PredicateCallback): Promise; 85 | 86 | some(fn: PredicateCallback): Promise; 87 | } 88 | 89 | declare function stream(iterable: Iterable | AsyncIterable): Stream; 90 | -------------------------------------------------------------------------------- /examples/browser/browser-adapter.js: -------------------------------------------------------------------------------- 1 | export default async function* (readable) { 2 | let exhausted = false; 3 | const reader = readable.getReader(); 4 | try { 5 | while (true) { 6 | const {value, done} = await reader.read(); 7 | exhausted = done; 8 | if (done) { 9 | break; 10 | } 11 | yield value; 12 | } 13 | } 14 | finally { 15 | reader.cancel(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/browser/fromFetch.js: -------------------------------------------------------------------------------- 1 | import iterable from './browser-adapter.js'; 2 | 3 | export default (path, otps = {}) => { 4 | return { 5 | [Symbol.asyncIterator]: async function* () { 6 | const res = await fetch(path); 7 | for await (const chunk of iterable(res.body)) { 8 | // decode 9 | yield String.fromCodePoint(...chunk); 10 | } 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /examples/csv-parser.js: -------------------------------------------------------------------------------- 1 | import {stream} from '../dist/bundle/module.js'; 2 | 3 | // take chunks stream (whether they come from file or network) and yield lines. 4 | export const lines = chunkStream => ({ 5 | [Symbol.asyncIterator]: async function* () { 6 | let remaining = ''; // chunk may ends in the middle of a line 7 | for await (const chunk of chunkStream) { 8 | const chunkLines = (remaining + chunk).split('\n'); 9 | remaining = chunkLines.pop(); 10 | yield* chunkLines; 11 | } 12 | yield remaining; 13 | } 14 | }); 15 | 16 | export async function parser(source) { 17 | const iterable = stream(lines(source)) 18 | .map(i => i.split(',')); 19 | 20 | const headers = await iterable 21 | .slice(0, 1) 22 | .reduce(acc => acc); 23 | 24 | return stream(iterable) 25 | .slice(1) 26 | .map(line => { 27 | const item = {}; 28 | for (let i = 0; i < headers.length; i++) { 29 | item[headers[i]] = line[i]; 30 | } 31 | return item; 32 | }); 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/node.js: -------------------------------------------------------------------------------- 1 | import {parser} from './csv-parser.js'; 2 | import fromFile from './node/fromFile.js'; 3 | 4 | //program 5 | (async function () { 6 | const stream = await parser(fromFile('./examples/fixture.csv')); 7 | for await (const line of stream) { 8 | console.log(line); 9 | } 10 | })(); 11 | -------------------------------------------------------------------------------- /examples/node/fromFile.js: -------------------------------------------------------------------------------- 1 | import {createReadStream} from 'fs'; 2 | 3 | // note: create a new stream on every asyncIterator invocation 4 | export default (file, opts = {encoding: 'utf8'}) => { 5 | return ({ 6 | [Symbol.asyncIterator]() { 7 | return createReadStream(file, opts)[Symbol.asyncIterator](); 8 | } 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | ### run for nodejs (version > 9) 4 | 5 | ``npm run example:node`` 6 | 7 | ### run for browser (chrome without transpilation) 8 | 9 | ``npm run example:browser`` 10 | 11 | [open in the browser](http://localhost:8080/examples/index.html -------------------------------------------------------------------------------- /examples/scripts/fixtures.js: -------------------------------------------------------------------------------- 1 | console.log('id,foo,bar,bim'); 2 | 3 | const int = () => Math.floor(Math.random() * 10); 4 | 5 | for (let i = 0; i<10000;i++){ 6 | console.log(`${i},${int()},${int()},${int()}`); 7 | } 8 | -------------------------------------------------------------------------------- /examples/scripts/rollup.node.js: -------------------------------------------------------------------------------- 1 | import node from 'rollup-plugin-node-resolve'; 2 | 3 | export default { 4 | input: './examples/node.js', 5 | output: [{ 6 | format: 'cjs' 7 | }], 8 | plugins:[node()] 9 | }; 10 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lorenzofox3/for-await", 3 | "version": "0.2.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/estree": { 8 | "version": "0.0.41", 9 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.41.tgz", 10 | "integrity": "sha512-rIAmXyJlqw4KEBO7+u9gxZZSQHaCNnIzYrnNmYVpgfJhxTqO0brCX0SYpqUTkVI5mwwUwzmtspLBGBKroMeynA==", 11 | "dev": true 12 | }, 13 | "@types/node": { 14 | "version": "13.1.1", 15 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.1.tgz", 16 | "integrity": "sha512-hx6zWtudh3Arsbl3cXay+JnkvVgCKzCWKv42C9J01N2T2np4h8w5X8u6Tpz5mj38kE3M9FM0Pazx8vKFFMnjLQ==", 17 | "dev": true 18 | }, 19 | "@types/resolve": { 20 | "version": "0.0.8", 21 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", 22 | "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", 23 | "dev": true, 24 | "requires": { 25 | "@types/node": "*" 26 | } 27 | }, 28 | "acorn": { 29 | "version": "7.1.0", 30 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", 31 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", 32 | "dev": true 33 | }, 34 | "ansi-regex": { 35 | "version": "2.1.1", 36 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 37 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 38 | "dev": true 39 | }, 40 | "ansi-styles": { 41 | "version": "2.2.1", 42 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 43 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 44 | "dev": true 45 | }, 46 | "argparse": { 47 | "version": "1.0.10", 48 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 49 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 50 | "dev": true, 51 | "requires": { 52 | "sprintf-js": "~1.0.2" 53 | } 54 | }, 55 | "builtin-modules": { 56 | "version": "3.1.0", 57 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", 58 | "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", 59 | "dev": true 60 | }, 61 | "chalk": { 62 | "version": "1.1.3", 63 | "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 64 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 65 | "dev": true, 66 | "requires": { 67 | "ansi-styles": "^2.2.1", 68 | "escape-string-regexp": "^1.0.2", 69 | "has-ansi": "^2.0.0", 70 | "strip-ansi": "^3.0.0", 71 | "supports-color": "^2.0.0" 72 | } 73 | }, 74 | "core-util-is": { 75 | "version": "1.0.2", 76 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 77 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 78 | "dev": true 79 | }, 80 | "diff": { 81 | "version": "2.2.3", 82 | "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", 83 | "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=", 84 | "dev": true 85 | }, 86 | "duplexer": { 87 | "version": "0.1.1", 88 | "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", 89 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", 90 | "dev": true 91 | }, 92 | "escape-string-regexp": { 93 | "version": "1.0.5", 94 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 95 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 96 | "dev": true 97 | }, 98 | "esprima": { 99 | "version": "4.0.1", 100 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 101 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 102 | "dev": true 103 | }, 104 | "estree-walker": { 105 | "version": "0.6.1", 106 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 107 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 108 | "dev": true 109 | }, 110 | "events-to-array": { 111 | "version": "1.1.2", 112 | "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", 113 | "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", 114 | "dev": true 115 | }, 116 | "figures": { 117 | "version": "1.7.0", 118 | "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", 119 | "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", 120 | "dev": true, 121 | "requires": { 122 | "escape-string-regexp": "^1.0.5", 123 | "object-assign": "^4.1.0" 124 | } 125 | }, 126 | "has-ansi": { 127 | "version": "2.0.0", 128 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 129 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 130 | "dev": true, 131 | "requires": { 132 | "ansi-regex": "^2.0.0" 133 | } 134 | }, 135 | "inherits": { 136 | "version": "2.0.3", 137 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 138 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 139 | "dev": true 140 | }, 141 | "is-finite": { 142 | "version": "1.0.2", 143 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 144 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 145 | "dev": true, 146 | "requires": { 147 | "number-is-nan": "^1.0.0" 148 | } 149 | }, 150 | "is-module": { 151 | "version": "1.0.0", 152 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 153 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 154 | "dev": true 155 | }, 156 | "is-reference": { 157 | "version": "1.1.4", 158 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", 159 | "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", 160 | "dev": true, 161 | "requires": { 162 | "@types/estree": "0.0.39" 163 | }, 164 | "dependencies": { 165 | "@types/estree": { 166 | "version": "0.0.39", 167 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 168 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 169 | "dev": true 170 | } 171 | } 172 | }, 173 | "isarray": { 174 | "version": "1.0.0", 175 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 176 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 177 | "dev": true 178 | }, 179 | "js-yaml": { 180 | "version": "3.13.1", 181 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 182 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 183 | "dev": true, 184 | "requires": { 185 | "argparse": "^1.0.7", 186 | "esprima": "^4.0.0" 187 | } 188 | }, 189 | "magic-string": { 190 | "version": "0.25.4", 191 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", 192 | "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", 193 | "dev": true, 194 | "requires": { 195 | "sourcemap-codec": "^1.4.4" 196 | } 197 | }, 198 | "number-is-nan": { 199 | "version": "1.0.1", 200 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 201 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 202 | "dev": true 203 | }, 204 | "object-assign": { 205 | "version": "4.1.1", 206 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 207 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 208 | "dev": true 209 | }, 210 | "parse-ms": { 211 | "version": "1.0.1", 212 | "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", 213 | "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", 214 | "dev": true 215 | }, 216 | "path-parse": { 217 | "version": "1.0.6", 218 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 219 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 220 | "dev": true 221 | }, 222 | "plur": { 223 | "version": "1.0.0", 224 | "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", 225 | "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", 226 | "dev": true 227 | }, 228 | "pretty-ms": { 229 | "version": "2.1.0", 230 | "resolved": "http://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", 231 | "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", 232 | "dev": true, 233 | "requires": { 234 | "is-finite": "^1.0.1", 235 | "parse-ms": "^1.0.0", 236 | "plur": "^1.0.0" 237 | } 238 | }, 239 | "process-nextick-args": { 240 | "version": "2.0.0", 241 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 242 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", 243 | "dev": true 244 | }, 245 | "readable-stream": { 246 | "version": "2.3.6", 247 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 248 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 249 | "dev": true, 250 | "requires": { 251 | "core-util-is": "~1.0.0", 252 | "inherits": "~2.0.3", 253 | "isarray": "~1.0.0", 254 | "process-nextick-args": "~2.0.0", 255 | "safe-buffer": "~5.1.1", 256 | "string_decoder": "~1.1.1", 257 | "util-deprecate": "~1.0.1" 258 | } 259 | }, 260 | "resolve": { 261 | "version": "1.14.1", 262 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.1.tgz", 263 | "integrity": "sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg==", 264 | "dev": true, 265 | "requires": { 266 | "path-parse": "^1.0.6" 267 | } 268 | }, 269 | "rollup": { 270 | "version": "1.27.14", 271 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.27.14.tgz", 272 | "integrity": "sha512-DuDjEyn8Y79ALYXMt+nH/EI58L5pEw5HU9K38xXdRnxQhvzUTI/nxAawhkAHUQeudANQ//8iyrhVRHJBuR6DSQ==", 273 | "dev": true, 274 | "requires": { 275 | "@types/estree": "*", 276 | "@types/node": "*", 277 | "acorn": "^7.1.0" 278 | } 279 | }, 280 | "rollup-plugin-commonjs": { 281 | "version": "10.1.0", 282 | "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", 283 | "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", 284 | "dev": true, 285 | "requires": { 286 | "estree-walker": "^0.6.1", 287 | "is-reference": "^1.1.2", 288 | "magic-string": "^0.25.2", 289 | "resolve": "^1.11.0", 290 | "rollup-pluginutils": "^2.8.1" 291 | } 292 | }, 293 | "rollup-plugin-node-resolve": { 294 | "version": "5.2.0", 295 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", 296 | "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", 297 | "dev": true, 298 | "requires": { 299 | "@types/resolve": "0.0.8", 300 | "builtin-modules": "^3.1.0", 301 | "is-module": "^1.0.0", 302 | "resolve": "^1.11.1", 303 | "rollup-pluginutils": "^2.8.1" 304 | } 305 | }, 306 | "rollup-pluginutils": { 307 | "version": "2.8.2", 308 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 309 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 310 | "dev": true, 311 | "requires": { 312 | "estree-walker": "^0.6.1" 313 | } 314 | }, 315 | "safe-buffer": { 316 | "version": "5.1.2", 317 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 318 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 319 | "dev": true 320 | }, 321 | "sourcemap-codec": { 322 | "version": "1.4.6", 323 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", 324 | "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", 325 | "dev": true 326 | }, 327 | "sprintf-js": { 328 | "version": "1.0.3", 329 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 330 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 331 | "dev": true 332 | }, 333 | "string_decoder": { 334 | "version": "1.1.1", 335 | "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 336 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 337 | "dev": true, 338 | "requires": { 339 | "safe-buffer": "~5.1.0" 340 | } 341 | }, 342 | "strip-ansi": { 343 | "version": "3.0.1", 344 | "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 345 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 346 | "dev": true, 347 | "requires": { 348 | "ansi-regex": "^2.0.0" 349 | } 350 | }, 351 | "supports-color": { 352 | "version": "2.0.0", 353 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 354 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 355 | "dev": true 356 | }, 357 | "tap-diff": { 358 | "version": "0.1.1", 359 | "resolved": "https://registry.npmjs.org/tap-diff/-/tap-diff-0.1.1.tgz", 360 | "integrity": "sha1-j78zM9hWQ/7qG/F1m5CCCwSjfd8=", 361 | "dev": true, 362 | "requires": { 363 | "chalk": "^1.1.1", 364 | "diff": "^2.2.1", 365 | "duplexer": "^0.1.1", 366 | "figures": "^1.4.0", 367 | "pretty-ms": "^2.1.0", 368 | "tap-parser": "^1.2.2", 369 | "through2": "^2.0.0" 370 | } 371 | }, 372 | "tap-parser": { 373 | "version": "1.3.2", 374 | "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-1.3.2.tgz", 375 | "integrity": "sha1-EgxQiciMPIp5PvKIhn3jIeGPjCI=", 376 | "dev": true, 377 | "requires": { 378 | "events-to-array": "^1.0.1", 379 | "inherits": "~2.0.1", 380 | "js-yaml": "^3.2.7", 381 | "readable-stream": "^2" 382 | } 383 | }, 384 | "through2": { 385 | "version": "2.0.3", 386 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", 387 | "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", 388 | "dev": true, 389 | "requires": { 390 | "readable-stream": "^2.1.5", 391 | "xtend": "~4.0.1" 392 | } 393 | }, 394 | "util-deprecate": { 395 | "version": "1.0.2", 396 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 397 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 398 | "dev": true 399 | }, 400 | "xtend": { 401 | "version": "4.0.1", 402 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 403 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 404 | "dev": true 405 | }, 406 | "zora": { 407 | "version": "3.1.8", 408 | "resolved": "https://registry.npmjs.org/zora/-/zora-3.1.8.tgz", 409 | "integrity": "sha512-AArEyKiLWi3eLXW2uRbfPvANfSQgV8VHoCuXCihCTQyUv7brFrghGbsUqKxqucc+QodQ1G2+O8Gpsz8RVpeiRQ==", 410 | "dev": true 411 | } 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lorenzofox3/for-await", 3 | "version": "0.2.1", 4 | "description": "operators and stream utilities for async iterators", 5 | "engines": { 6 | "node": ">=9.3.0" 7 | }, 8 | "directories": { 9 | "test": "test" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/lorenzofox3/for-await.git" 14 | }, 15 | "main": "./dist/bundle/index", 16 | "module": "./dist/bundle/module.js", 17 | "types": "./dist/bundle/index.d.ts", 18 | "scripts": { 19 | "build:clean": "rm -rf ./dist && mkdir -p ./dist/bundle ./dist/declarations", 20 | "build:bundle": "rollup -c ./rollup/build.js && cp ./src/index.d.ts ./dist/declarations/", 21 | "build": "npm run build:clean && npm run build:bundle", 22 | "test": "rollup -c ./rollup/test.js --output.format cjs | node", 23 | "test:ci": "rollup -c ./rollup/test.js --output.format cjs | node | tap-diff", 24 | "example:node": "rollup -c ./examples/scripts/rollup.node.js | node" 25 | }, 26 | "files": [ 27 | "dist/bundle", 28 | "dist/declarations" 29 | ], 30 | "keywords": [ 31 | "stream", 32 | "for-await", 33 | "reactive-programming", 34 | "iterator", 35 | "async-iterator", 36 | "asyncIterator", 37 | "generator", 38 | "async" 39 | ], 40 | "author": "Laurent Renard", 41 | "license": "MIT", 42 | "devDependencies": { 43 | "rollup": "^1.27.14", 44 | "rollup-plugin-commonjs": "^10.1.0", 45 | "rollup-plugin-node-resolve": "^5.2.0", 46 | "tap-diff": "^0.1.1", 47 | "zora": "^3.1.8" 48 | }, 49 | "dependencies": {} 50 | } 51 | -------------------------------------------------------------------------------- /rollup/build.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: './src/index.js', 3 | output: [{ 4 | file: './dist/bundle/index.js', 5 | format: 'cjs' 6 | }, { 7 | file: './dist/bundle/index.mjs', 8 | format: 'es' 9 | }, { 10 | file: './dist/bundle/module.js', 11 | format: 'es' 12 | }, { 13 | file: './dist/bundle/for-await.js', 14 | format: 'iife', 15 | name: 'ForAwait' 16 | }] 17 | }; 18 | -------------------------------------------------------------------------------- /rollup/test.js: -------------------------------------------------------------------------------- 1 | import node from 'rollup-plugin-node-resolve'; 2 | import cjs from 'rollup-plugin-commonjs'; 3 | 4 | export default { 5 | input: './test/index.js', 6 | plugins: [node(), cjs()] 7 | }; 8 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface MapCallback { 4 | (item: T, index: number, iterable: AsyncIterable): U | Promise; 5 | } 6 | 7 | declare function map(callback: MapCallback): (iterable: AsyncIterable) => AsyncIterable; 8 | declare function map(callback: MapCallback, iterable: AsyncIterable): AsyncIterable; 9 | 10 | interface PredicateCallback { 11 | (item: T, index: number, iterable: AsyncIterable): boolean; 12 | } 13 | 14 | declare function filter(callback: PredicateCallback): (iterable: AsyncIterable) => AsyncIterable; 15 | declare function filter(callback: PredicateCallback, iterable: AsyncIterable): AsyncIterable; 16 | 17 | declare function take(number: number): (iterable: AsyncIterable) => AsyncIterable; 18 | declare function take(number: number, iterable: AsyncIterable): AsyncIterable; 19 | 20 | declare function skip(number: number): (iterable: AsyncIterable) => AsyncIterable; 21 | declare function skip(number: number, iterable: AsyncIterable): AsyncIterable; 22 | 23 | type ItemOrAsyncIterable = T | AsyncIterable; 24 | 25 | declare function flatMap(fn: MapCallback): (iterable: AsyncIterable>) => AsyncIterable; 26 | declare function flatMap(fn: MapCallback, iterable: AsyncIterable>): AsyncIterable; 27 | 28 | declare function slice(iterable: AsyncIterable): AsyncIterable; 29 | declare function slice(start: number, iterable: AsyncIterable): AsyncIterable; 30 | declare function slice(start: number, end: number, iterable: AsyncIterable): AsyncIterable; 31 | declare function slice(start?: number, end?: number): (iterable: AsyncIterable) => AsyncIterable; 32 | 33 | declare function concat(...args: ItemOrAsyncIterable[]): AsyncIterable; 34 | 35 | interface ReduceCallback { 36 | (accumulator: U, currentItem: T, index: number, iterable: AsyncIterable): Promise; 37 | } 38 | 39 | declare function reduce(fn: ReduceCallback, initialValue?: U): (iterable: AsyncIterable) => Promise; 40 | declare function reduce(fn: ReduceCallback, iterable: AsyncIterable): Promise; 41 | declare function reduce(fn: ReduceCallback, initialValue: U, iterable: AsyncIterable): Promise; 42 | 43 | interface TupleCallback { 44 | (item: T, index: number, iterable: AsyncIterable): boolean; 45 | } 46 | 47 | declare function find(fn: TupleCallback): (iterable: AsyncIterable) => Promise; 48 | declare function find(fn: TupleCallback, iterable: AsyncIterable): Promise; 49 | 50 | declare function findIndex(fn: TupleCallback): (iterable: AsyncIterable) => Promise; 51 | declare function findIndex(fn: TupleCallback, iterable: AsyncIterable): Promise; 52 | 53 | declare function includes(item: T, from ?: number): (iterable: AsyncIterable) => Promise; 54 | declare function includes(item: T, iterable: AsyncIterable): Promise; 55 | declare function includes(item: T, from: number, iterable: AsyncIterable): Promise; 56 | 57 | declare function every(fn: PredicateCallback, iterable: AsyncIterable): Promise; 58 | declare function every(fn: PredicateCallback): (iterable: AsyncIterable) => Promise; 59 | 60 | declare function some(fn: PredicateCallback, iterable: AsyncIterable): Promise; 61 | declare function some(fn: PredicateCallback): (iterable: AsyncIterable) => Promise; 62 | 63 | declare function from(iterable: Iterable): AsyncIterable; 64 | 65 | export interface Stream extends AsyncIterable { 66 | map(fn: MapCallback): Stream; 67 | 68 | filter(fn: PredicateCallback): Stream; 69 | 70 | flatMap(fn: MapCallback): Stream; 71 | 72 | slice(start?: number, end?: number): Stream; 73 | 74 | concat(...values: ItemOrAsyncIterable[]): Stream; 75 | 76 | reduce(fn: ReduceCallback, initialValue?: U): Promise; 77 | 78 | find(fn: TupleCallback): Promise; 79 | 80 | findIndex(fn: TupleCallback): Promise; 81 | 82 | includes(item: T, from?: number): Promise; 83 | 84 | every(fn: PredicateCallback): Promise; 85 | 86 | some(fn: PredicateCallback): Promise; 87 | } 88 | 89 | declare function stream(iterable: Iterable | AsyncIterable): Stream; 90 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export * from './lib/operators'; 2 | export {stream, toAsync as from} from './lib/stream'; 3 | -------------------------------------------------------------------------------- /src/lib/operators.js: -------------------------------------------------------------------------------- 1 | import {toIterable, curry, toCurriedIterable} from './util'; 2 | 3 | export const map = toCurriedIterable(async function* (fn, asyncIterable) { 4 | let index = 0; 5 | for await (const i of asyncIterable) { 6 | yield fn(i, index, asyncIterable); 7 | index++; 8 | } 9 | }); 10 | 11 | export const filter = toCurriedIterable(async function* (fn, asyncIterable) { 12 | let index = 0; 13 | for await (const i of asyncIterable) { 14 | if (fn(i, index, asyncIterable) === true) { 15 | yield i; 16 | } 17 | index++; 18 | } 19 | }); 20 | 21 | export const take = toCurriedIterable(async function* (number, asyncIterable) { 22 | let count = 1; 23 | for await (const i of asyncIterable) { 24 | if (number !== undefined && count > number) { 25 | break; 26 | } 27 | yield i; 28 | count++; 29 | } 30 | }); 31 | 32 | export const skip = toCurriedIterable(async function* (limit, asyncIterable) { 33 | let count = 0; 34 | for await (const i of asyncIterable) { 35 | if (count < limit) { 36 | count++; 37 | continue; 38 | } 39 | yield i; 40 | } 41 | }); 42 | 43 | export const flatMap = toCurriedIterable(async function* (fn, asyncIterable) { 44 | for await (const i of asyncIterable) { 45 | if (i[Symbol.asyncIterator]) { 46 | yield* map(fn, i); 47 | } else { 48 | yield fn(i); 49 | } 50 | } 51 | }); 52 | 53 | const acutalSlice = toIterable(async function* (s, e, iterable) { 54 | const toSkip = skip(s); 55 | const toTake = take(e !== void 0 ? e - s : e); 56 | for await (const i of toTake(toSkip(iterable))) { 57 | yield i; 58 | } 59 | }); 60 | export const slice = (start, end, asyncIterable) => { 61 | let s = start || 0; 62 | let e = end; 63 | let iterable = asyncIterable; 64 | if (start && start[Symbol.asyncIterator] !== void 0) { 65 | iterable = start; 66 | s = 0; 67 | e = void 0; 68 | } else if (end && end[Symbol.asyncIterator] !== void 0) { 69 | iterable = end; 70 | s = start; 71 | e = void 0; 72 | } else if (asyncIterable === void 0) { 73 | return iterable => acutalSlice(s, e, iterable); 74 | } 75 | return acutalSlice(s, e, iterable); 76 | }; 77 | 78 | export const concat = toIterable(async function* (...values) { 79 | for (const i of values) { 80 | if (i[Symbol.asyncIterator]) { 81 | yield* i; 82 | } else { 83 | yield i; 84 | } 85 | } 86 | }); 87 | 88 | const actualReduce = async (fn, initialValue, asyncIterable) => { 89 | let index = -1; 90 | const iterator = asyncIterable[Symbol.asyncIterator](); 91 | const next = async () => { 92 | index++; 93 | return iterator.next(); 94 | }; 95 | let acc = initialValue; 96 | 97 | if (initialValue === void 0) { 98 | acc = (await next()).value; 99 | } 100 | 101 | while (true) { 102 | const {value, done} = await next(); 103 | if (done === true) { 104 | return acc; 105 | } 106 | acc = fn(acc, value, index, asyncIterable); 107 | } 108 | }; 109 | export const reduce = (fn, initVal, asyncIterable) => { 110 | let acc = initVal; 111 | let iterable = asyncIterable; 112 | 113 | if (initVal && initVal[Symbol.asyncIterator] !== void 0) { 114 | iterable = initVal; 115 | acc = void 0; 116 | } 117 | 118 | if (iterable === void 0) { 119 | return iterable => actualReduce(fn, acc, iterable); 120 | } 121 | 122 | return actualReduce(fn, acc, iterable); 123 | }; 124 | 125 | const findTuple = async (fn, asyncIterable) => { 126 | let index = 0; 127 | for await (const i of asyncIterable) { 128 | if (fn(i, index, asyncIterable)) { 129 | return {value: i, index: index}; 130 | } 131 | index++; 132 | } 133 | return {value: void 0, index: -1}; 134 | }; 135 | 136 | export const find = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).value); 137 | 138 | export const findIndex = curry(async (fn, asyncIterable) => (await findTuple(fn, asyncIterable)).index); 139 | 140 | const actualIncludes = async (item, from, iterable) => { 141 | const strictEqualToItem = findIndex(x => x === item); 142 | return (await strictEqualToItem(skip(from, iterable))) > -1; 143 | }; 144 | export const includes = (item, from, asyncIterable) => { 145 | let start = from; 146 | let iterable = asyncIterable; 147 | 148 | if (from && from[Symbol.asyncIterator] !== void 0) { 149 | start = 0; 150 | iterable = from; 151 | } 152 | 153 | if (iterable === void 0) { 154 | return iterable => actualIncludes(item, start, iterable); 155 | } 156 | 157 | return actualIncludes(item, start, iterable); 158 | }; 159 | 160 | export const every = curry(async (fn, asyncIterable) => { 161 | let index = 0; 162 | for await(const i of asyncIterable) { 163 | if (!fn(i, index, asyncIterable)) { 164 | return false; 165 | } 166 | index++; 167 | } 168 | return true; 169 | }); 170 | 171 | export const some = curry(async (fn, asyncIterable) => { 172 | let index = 0; 173 | for await(const i of asyncIterable) { 174 | if (fn(i, index, asyncIterable)) { 175 | return true; 176 | } 177 | index++; 178 | } 179 | return false; 180 | }); 181 | -------------------------------------------------------------------------------- /src/lib/stream.js: -------------------------------------------------------------------------------- 1 | import * as operators from './operators'; 2 | import {toIterable} from './util'; 3 | 4 | /* 5 | The iterable won't always be consumed with a for await statement (which implicitly convert an iterable into a asyncIterable) so we need to explicitly make it async iterable 6 | for await (const t of [1,2,3,4,5]){ 7 | //no problem 8 | } 9 | 10 | but 11 | 12 | const iterator = [1,2,3][Symbol.asyncIterator](); 13 | //problem 14 | */ 15 | export const toAsync = toIterable(async function* (iterable) { 16 | yield* iterable; 17 | }); 18 | 19 | export const proto = { 20 | [Symbol.asyncIterator]() { 21 | return this._source[Symbol.asyncIterator](); 22 | }, 23 | map(fn) { 24 | return stream(operators.map(fn, this)); 25 | }, 26 | filter(fn) { 27 | return stream(operators.filter(fn, this)); 28 | }, 29 | flatMap(fn) { 30 | return stream(operators.flatMap(fn, this)); 31 | }, 32 | slice(start = 0, end = void 0) { 33 | return stream(operators.slice(start, end, this)); 34 | }, 35 | concat(...values) { 36 | return stream(operators.concat(this, ...values)); 37 | }, 38 | reduce(fn, initialValue) { 39 | return operators.reduce(fn, initialValue, this); 40 | }, 41 | find(fn) { 42 | return operators.find(fn, this); 43 | }, 44 | findIndex(fn) { 45 | return operators.findIndex(fn, this); 46 | }, 47 | includes(item, from = 0) { 48 | return operators.includes(item, from, this); 49 | }, 50 | every(fn) { 51 | return operators.every(fn, this); 52 | }, 53 | some(fn) { 54 | return operators.some(fn, this); 55 | } 56 | }; 57 | 58 | export const stream = iterable => { 59 | const source = !iterable[Symbol.asyncIterator] ? toAsync(iterable) : iterable; // we make a difference as any wrap of iterable has performance impact (for the moment) 60 | return Object.create(proto, {_source: {value: source}}); 61 | }; 62 | -------------------------------------------------------------------------------- /src/lib/util.js: -------------------------------------------------------------------------------- 1 | // with two arguments 2 | export const curry = (fn) => (a, b) => b === void 0 ? b => fn(a, b) : fn(a, b); 3 | export const toCurriedIterable = gen => curry((a, b) => ({ 4 | [Symbol.asyncIterator]() { 5 | return gen(a, b); 6 | } 7 | })); 8 | export const toIterable = gen => (...args) => ({ 9 | [Symbol.asyncIterator]() { 10 | return gen(...args); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /test/concat.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {concat} from '../src/lib/operators'; 3 | import {breakableCounter, counterGenerator} from './util/source.js'; 4 | 5 | export default test('concat operator test suite', t => { 6 | 7 | t.test('basic', async t => { 8 | let i = 0; 9 | const to3 = counterGenerator(); 10 | const stream = concat(666, [1, 2], {foo: 'bar'}, to3, 'foo'); 11 | 12 | const result = []; 13 | for await (const item of stream) { 14 | i++; 15 | result.push(item); 16 | } 17 | 18 | t.equal(i, 7, 'should have seen 7 iterations'); 19 | t.deepEqual(result, [666, [1, 2], {foo: 'bar'}, 0, 1, 2, 'foo'], 'should have seen the flattened items'); 20 | }); 21 | 22 | t.test('forward control flow events', async t => { 23 | const to3 = breakableCounter(); 24 | const to3Bis = breakableCounter(); 25 | let i = 0; 26 | const stream = concat(to3, to3Bis); 27 | const result = []; 28 | for await (const item of stream) { 29 | result.push(item); 30 | i++; 31 | if (i >= 4) { 32 | break; 33 | } 34 | } 35 | 36 | t.equal(i, 4, 'should have seen 4 iterations only'); 37 | t.deepEqual(result, [0, 1, 2, 0], 'should have collected the flattened items'); 38 | t.equal(to3.done, false, 'should have been exhausted regularly'); 39 | t.equal(to3Bis.done, true, 'should have called the return hook'); 40 | t.equal(to3.index, 3, 'should have been pulled 3 times'); 41 | t.equal(to3Bis.index, 1, 'should have been pulled 1 time only'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/every.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {counterGenerator} from './util/source.js'; 3 | import {every} from '../src/lib/operators'; 4 | 5 | export default test('every test suite', t => { 6 | t.test('basic', async t => { 7 | let i = 0; 8 | const lowerThan5 = await every((item, index) => { 9 | t.equal(i, index, 'should have forwarded the index'); 10 | i++; 11 | return item <= 5; 12 | }, counterGenerator(5)); 13 | 14 | t.equal(i, 5, 'should have done 5 iterations'); 15 | t.equal(lowerThan5, true, 'all items are lower than 5'); 16 | 17 | i = 0; 18 | 19 | const odd = await every((item, index) => { 20 | t.equal(i, index, 'should have forwarded the index'); 21 | i++; 22 | return item % 2 === 0; 23 | }, counterGenerator(5)); 24 | 25 | t.equal(i, 2, 'should have done 2 iterations (until the first non odd number'); 26 | t.equal(odd, false, 'not every item is odd'); 27 | }); 28 | 29 | t.test('curried', async t => { 30 | let i = 0; 31 | const lowerThan5 = await every((item, index) => { 32 | t.equal(i, index, 'should have forwarded the index'); 33 | i++; 34 | return item <= 5; 35 | })(counterGenerator(5)); 36 | 37 | t.equal(i, 5, 'should have done 5 iterations'); 38 | t.equal(lowerThan5, true, 'all items are lower than 5'); 39 | 40 | i = 0; 41 | 42 | const odd = await every((item, index) => { 43 | t.equal(i, index, 'should have forwarded the index'); 44 | i++; 45 | return item % 2 === 0; 46 | })(counterGenerator(5)); 47 | 48 | t.equal(i, 2, 'should have done 2 iterations (until the first non odd number'); 49 | t.equal(odd, false, 'not every item is odd'); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /test/filter.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {filter} from '../src/lib/operators'; 3 | import {breakableCounter, counterGenerator, counterIterable} from './util/source.js'; 4 | 5 | export default test('filter operator test suite', t => { 6 | 7 | t.test('basic filter', async t => { 8 | let i = 0; 9 | const to3 = counterGenerator(); 10 | const filterStream = filter((x, index) => { 11 | t.equal(index, x, 'should have passed the index'); 12 | return x % 2 === 0; 13 | }, to3); 14 | 15 | let result = []; 16 | 17 | for await (const item of filterStream) { 18 | result.push(item); 19 | i++; 20 | } 21 | 22 | t.deepEqual(result, [0, 2], 'should have kept odd numbers'); 23 | 24 | for await (const item of filterStream) { 25 | i++; 26 | } 27 | 28 | t.equal(i, 2, 'should have seen 3 iterations only'); 29 | }); 30 | 31 | t.test('filter: forward the consumable nature of the underlying asyncIterator', async t => { 32 | let i = 0; 33 | const to3 = counterIterable(); 34 | const filterStream = filter((x, index) => { 35 | t.equal(index, x, 'should have passed the index'); 36 | return x % 2 === 0; 37 | }, to3); 38 | 39 | let result = []; 40 | 41 | for await (const item of filterStream) { 42 | result.push(item); 43 | i++; 44 | } 45 | 46 | t.deepEqual(result, [0, 2], 'should have kept odd numbers'); 47 | 48 | result = []; 49 | 50 | for await (const item of filterStream) { 51 | result.push(item); 52 | i++; 53 | } 54 | 55 | t.deepEqual(result, [0, 2], 'should have kept odd numbers again'); 56 | t.equal(i, 4, 'should have seen 6 iterations'); 57 | 58 | }); 59 | 60 | t.test('should forward control flow event', async t => { 61 | let i = 0; 62 | const to3 = breakableCounter(); 63 | const filteredStream = filter(x => x % 2 === 0, to3); 64 | for await (const item of filteredStream) { 65 | i++; 66 | break; 67 | } 68 | 69 | t.equal(i, 1, 'should have done a single iteration'); 70 | 71 | t.equal(to3.done, true, 'should have called the return of underlying iterator'); 72 | t.equal(to3.index, 1, 'should have pulled one single time'); 73 | 74 | }); 75 | 76 | t.test('curried filter', async t => { 77 | const odd = filter(x => x % 2 === 0); 78 | let i = 0; 79 | let result = []; 80 | for await (const item of odd(counterGenerator())) { 81 | result.push(item); 82 | i++; 83 | } 84 | 85 | t.deepEqual(result, [0, 2], 'should have kept odd numbers only'); 86 | 87 | result = []; 88 | 89 | i = 0; 90 | for await (const item of odd(counterGenerator())) { 91 | result.push(item); 92 | i++; 93 | } 94 | 95 | t.deepEqual(result, [0, 2], 'should have kept odd numbers only'); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/find.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {find, findIndex} from '../src/lib/operators'; 3 | import {counterGenerator} from './util/source.js'; 4 | 5 | export default test('find test suite', t => { 6 | 7 | t.test('find', async t => { 8 | let i = 0; 9 | const value = await find((item, index) => { 10 | t.equal(index, i, 'should have forwarded the index'); 11 | i++; 12 | return item >= 3; 13 | }, counterGenerator(5)); 14 | 15 | t.equal(value, 3, 'should have found 3'); 16 | t.equal(i, 4, 'should have broken after four iterations'); 17 | }); 18 | 19 | t.test('find: no result', async t => { 20 | let i = 0; 21 | const value = await find((item, index) => { 22 | t.equal(index, i, 'should have forwarded the index'); 23 | i++; 24 | return item >= 666; 25 | }, counterGenerator(5)); 26 | 27 | t.equal(value, void 0, 'result value should be undefined'); 28 | t.equal(i, 5, 'should have iterated over the whole stream'); 29 | }); 30 | 31 | t.test('find curried', async t => { 32 | let i = 0; 33 | 34 | const greaterOrEqualTo3 = find((item, index) => { 35 | t.equal(index, i, 'should have forwarded the index'); 36 | i++; 37 | return item >= 3; 38 | }); 39 | 40 | const value = await greaterOrEqualTo3(counterGenerator(5)); 41 | 42 | t.equal(value, 3, 'should have found 3'); 43 | t.equal(i, 4, 'should have broken after four iterations'); 44 | }); 45 | 46 | t.test('find curried with no result', async t => { 47 | let i = 0; 48 | 49 | const greaterOrEqualToEvil = find((item, index) => { 50 | t.equal(index, i, 'should have forwarded the index'); 51 | i++; 52 | return item >= 666; 53 | }); 54 | 55 | const value = await greaterOrEqualToEvil(counterGenerator(5)); 56 | 57 | t.equal(value, void 0, 'retuned value should be undefined'); 58 | t.equal(i, 5, 'should have iterated the whole stream'); 59 | }); 60 | 61 | t.test('find Index', async t => { 62 | let i = 0; 63 | const value = await findIndex((item, index) => { 64 | t.equal(index, i, 'should have forwarded the index'); 65 | i++; 66 | return item >= 3; 67 | }, counterGenerator(5)); 68 | 69 | t.equal(value, 3, 'should have return the index of value 3'); 70 | t.equal(i, 4, 'should have broken after four iterations'); 71 | }); 72 | 73 | t.test('find Index: no result', async t => { 74 | let i = 0; 75 | const value = await findIndex((item, index) => { 76 | t.equal(index, i, 'should have forwarded the index'); 77 | i++; 78 | return item >= 666; 79 | }, counterGenerator(5)); 80 | 81 | t.equal(value, -1, 'result index should be -1'); 82 | t.equal(i, 5, 'should have iterated over the whole stream'); 83 | }); 84 | 85 | t.test('find Index curried', async t => { 86 | let i = 0; 87 | 88 | const greaterOrEqualTo3 = findIndex((item, index) => { 89 | t.equal(index, i, 'should have forwarded the index'); 90 | i++; 91 | return item >= 3; 92 | }); 93 | 94 | const value = await greaterOrEqualTo3(counterGenerator(5)); 95 | 96 | t.equal(value, 3, 'should have return the index of value 3'); 97 | t.equal(i, 4, 'should have broken after four iterations'); 98 | }); 99 | 100 | t.test('find Index curried with no result', async t => { 101 | let i = 0; 102 | 103 | const greaterOrEqualToEvil = findIndex((item, index) => { 104 | t.equal(index, i, 'should have forwarded the index'); 105 | i++; 106 | return item >= 666; 107 | }); 108 | 109 | const value = await greaterOrEqualToEvil(counterGenerator(5)); 110 | 111 | t.equal(value, -1, 'result index should be -1'); 112 | t.equal(i, 5, 'should have iterated over the whole stream'); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/flatMap.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {flatMap} from '../src/lib/operators'; 3 | import {counterGenerator} from './util/source'; 4 | 5 | export default test('flat map operator', t => { 6 | t.test('basic: should simply map if items are not asyncIterable', async t => { 7 | let index = 0; 8 | const gen = async function* () { 9 | yield 0; 10 | index++; 11 | yield 'foo'; 12 | index++; 13 | yield ['bar']; 14 | index++; 15 | yield false; 16 | }; 17 | 18 | const result = []; 19 | let iter = 0; 20 | const iterator = flatMap(x => x, gen()); 21 | 22 | for await (const item of iterator) { 23 | iter++; 24 | result.push(item); 25 | } 26 | 27 | for await (const item of iterator) { 28 | iter++; 29 | } 30 | 31 | t.equal(iter, 4, 'should have seen 4 iterations only'); 32 | t.deepEqual(result, [0, 'foo', ['bar'], false]); 33 | }); 34 | 35 | t.test('basic: should flatten asyncIterator items', async t => { 36 | let index = 0; 37 | const gen = async function* () { 38 | yield 666; 39 | index++; 40 | yield counterGenerator(); 41 | index++; 42 | yield ['bar']; 43 | index++; 44 | yield false; 45 | }; 46 | 47 | const result = []; 48 | let iter = 0; 49 | for await (const item of flatMap(x => x, gen())) { 50 | iter++; 51 | result.push(item); 52 | } 53 | t.equal(index, 3, 'should have seen 3 index increments'); 54 | t.equal(iter, 6, 'should have seen 6 iterations'); 55 | t.deepEqual(result, [666, 0, 1, 2, ['bar'], false]); 56 | }); 57 | 58 | t.test('should forward the consumable nature of the asyncIterable', async t => { 59 | let index = 0; 60 | const gen = async function* () { 61 | yield 666; 62 | index++; 63 | yield counterGenerator(); 64 | index++; 65 | yield ['bar']; 66 | index++; 67 | yield false; 68 | }; 69 | 70 | let result = []; 71 | let iter = 0; 72 | const iterator = flatMap(x => x, { 73 | [Symbol.asyncIterator]: gen 74 | }); 75 | 76 | for await (const item of iterator) { 77 | iter++; 78 | result.push(item); 79 | } 80 | 81 | t.equal(iter, 6, 'should have seen 6 iterations'); 82 | t.deepEqual(result, [666, 0, 1, 2, ['bar'], false]); 83 | 84 | iter = 0; 85 | result = []; 86 | 87 | for await (const item of iterator) { 88 | iter++; 89 | result.push(item); 90 | } 91 | 92 | 93 | t.equal(iter, 6, 'should have seen 6 iterations again'); 94 | t.deepEqual(result, [666, 0, 1, 2, ['bar'], false]); 95 | }); 96 | 97 | t.test('forward control flow event', async t => { 98 | 99 | let i = 0; 100 | let done = false; 101 | 102 | const iterator = { 103 | [Symbol.asyncIterator]() { 104 | return this; 105 | }, 106 | async next() { 107 | if (done === true || i > 2) { 108 | return {done: true}; 109 | } 110 | 111 | const value = {value: counterGenerator(), done: false}; 112 | i++; 113 | return value; 114 | }, 115 | 116 | async return() { 117 | done = true; 118 | return {done: true}; 119 | } 120 | }; 121 | 122 | let iter = 0; 123 | const result = []; 124 | for await (const item of flatMap(x => x, iterator)) { 125 | iter++; 126 | result.push(item); 127 | break; 128 | } 129 | 130 | t.equal(iter, 1, 'should have pulled one item of the main stream'); 131 | t.deepEqual(result, [0], 'should have iterated only on the first item of the sub stream'); 132 | t.equal(done, true, 'should have called the return hook of the source stream'); 133 | 134 | }); 135 | }); 136 | 137 | -------------------------------------------------------------------------------- /test/includes.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {counterGenerator} from './util/source.js'; 3 | import {includes} from '../src/lib/operators'; 4 | 5 | export default test('includes test suite', t => { 6 | t.test('includes', async t => { 7 | const has3 = await includes(3, counterGenerator(5)); 8 | t.equal(has3, true, 'should have 3'); 9 | 10 | const has666 = await includes(666, counterGenerator(5)); 11 | t.equal(has666, false, 'should not have 666'); 12 | 13 | const has1 = await includes(0, 1, counterGenerator(5)); 14 | t.equal(has1, false, 'should not have 0 when starting at 1'); 15 | 16 | const has5 = await includes(5, 3, counterGenerator(10)); 17 | t.equal(has5, true, 'should have 5 when starting at 3'); 18 | }); 19 | 20 | t.test('includes curried', async t => { 21 | const has3 = await includes(3)(counterGenerator(5)); 22 | t.equal(has3, true, 'should have 3'); 23 | 24 | const has666 = await includes(666)(counterGenerator(5)); 25 | t.equal(has666, false, 'should not have 666'); 26 | 27 | const has1 = await includes(0, 1)(counterGenerator(5)); 28 | t.equal(has1, false, 'should not have 0 when starting at 1'); 29 | 30 | const has5 = await includes(5, 3)(counterGenerator(10)); 31 | t.equal(has5, true, 'should have 5 when starting at 3'); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import util from './utils'; 2 | import stream from './stream'; 3 | import map from './map'; 4 | import filter from './filter'; 5 | import take from './take'; 6 | import skip from './skip'; 7 | import flatMap from './flatMap'; 8 | import concat from './concat'; 9 | import slice from './slice'; 10 | import reduce from './reduce'; 11 | import find from './find'; 12 | import include from './includes'; 13 | import every from './every'; 14 | import some from './some'; 15 | -------------------------------------------------------------------------------- /test/map.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {map} from '../src/lib/operators'; 3 | import {counterIterable, breakableCounter, counterGenerator} from './util/source.js'; 4 | 5 | export default test('map operator test suite', t => { 6 | 7 | t.test('basic', async t => { 8 | let i = 0; 9 | const to3 = counterGenerator(); 10 | const mappedStream = map((x, index) => { 11 | t.equal(index, x, 'should have passed the index'); 12 | return x * x; 13 | }, to3); 14 | 15 | for await (const item of mappedStream) { 16 | t.equal(item, i * i, `should see the square value of ${i} (${i * i})`); 17 | i++; 18 | } 19 | 20 | for await (const item of mappedStream) { 21 | i++; 22 | } 23 | 24 | t.equal(i, 3, 'should have seen 3 iterations only'); 25 | 26 | }); 27 | 28 | t.test('forward the consumable nature of the underlying asyncIterable', async t => { 29 | let i = 0; 30 | const to3 = counterIterable(); 31 | const mappedStream = map((x, index) => { 32 | t.equal(index, x, 'should have passed the index'); 33 | return x * x; 34 | }, to3); 35 | 36 | for await (const item of mappedStream) { 37 | t.equal(item, i * i, `should see the square value of ${i} (${i * i})`); 38 | i++; 39 | } 40 | 41 | for await (const item of mappedStream) { 42 | t.equal(item, (i - 3) * (i - 3), `should see the square value of ${i - 3} (${(i - 3) * (i - 3)})`); 43 | i++; 44 | } 45 | 46 | t.equal(i, 6, 'should have seen 6 iterations'); 47 | }); 48 | 49 | t.test('forward control flow event', async t => { 50 | let i = 0; 51 | const to3 = breakableCounter(); 52 | const mappedStream = map(x => x * x, to3); 53 | for await (const item of mappedStream) { 54 | i++; 55 | break; 56 | } 57 | 58 | t.equal(i, 1, 'should have done a single iteration'); 59 | 60 | t.equal(to3.done, true, 'should have called the return of underlying iterator'); 61 | t.equal(to3.index, 1, 'should have pulled one single time'); 62 | 63 | }); 64 | 65 | t.test('curried map', async t => { 66 | const square = map(x => x * x); 67 | let i = 0; 68 | for await (const item of square(counterGenerator())) { 69 | t.equal(item, i * i, `should see the square value of ${i} (${i * i})`); 70 | i++; 71 | } 72 | 73 | i = 0; 74 | for await (const item of square(counterGenerator())) { 75 | t.equal(item, i * i, `should see the square value of ${i} (${i * i}) from an other iterable`); 76 | i++; 77 | } 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/reduce.js: -------------------------------------------------------------------------------- 1 | import {reduce} from '../src/lib/operators'; 2 | import {test} from 'zora'; 3 | import {counterGenerator} from './util/source.js'; 4 | 5 | export default test('test suite for reduce', t => { 6 | 7 | t.test('basic', async t => { 8 | let index = 0; 9 | const value = await reduce((acc, curr, i) => { 10 | t.equal(i, index, 'should have forwarded the index to the callback'); 11 | index++; 12 | return acc + curr; 13 | }, 0, counterGenerator(5)); 14 | 15 | t.equal(value, 10, 'should equal to the sum of 0+1+2+3+4'); 16 | }); 17 | 18 | t.test('with no initial value', async t => { 19 | let index = 1; 20 | const value = await reduce((acc, curr, i) => { 21 | t.equal(i, index, 'should have forwarded the index to the callback'); 22 | index++; 23 | return acc + curr; 24 | }, counterGenerator(5)); 25 | 26 | t.equal(value, 10, 'should equal to the sum of 0+1+2+3+4'); 27 | }); 28 | 29 | t.test('curried', async t => { 30 | let index = 0; 31 | const reducer = reduce((acc, curr, i) => { 32 | t.equal(i, index, 'should have forwarded the index to the callback'); 33 | index++; 34 | return acc + curr; 35 | }, 0); 36 | 37 | const value = await reducer(counterGenerator(5)); 38 | t.equal(value, 10, 'should equal to the sum of 0+1+2+3+4'); 39 | }); 40 | 41 | t.test('curried with no initial value', async t => { 42 | let index = 1; 43 | const reducer = reduce((acc, curr, i) => { 44 | t.equal(i, index, 'should have forwarded the index to the callback'); 45 | index++; 46 | return acc + curr; 47 | }); 48 | 49 | const value = await reducer(counterGenerator(5)); 50 | t.equal(value, 10, 'should equal to the sum of 0+1+2+3+4'); 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /test/skip.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {skip} from '../src/lib/operators'; 3 | import {breakableCounter, counterGenerator, counterIterable} from './util/source.js'; 4 | 5 | export default test('skip operator', t => { 6 | 7 | t.test('basic', async t => { 8 | let i = 1; 9 | const to3 = counterGenerator(); 10 | const stream = skip(1, to3); 11 | 12 | 13 | for await (const item of stream) { 14 | t.equal(item, i, `should see item ${i}`); 15 | i++; 16 | } 17 | 18 | for await (const item of stream) { 19 | i++; 20 | } 21 | 22 | t.equal(i - 1, 2, 'should have seen 2 iterations only'); 23 | 24 | }); 25 | 26 | t.test('forward the consumable nature of the underlying asyncIterator', async t => { 27 | let i = 1; 28 | const to3 = counterIterable(); 29 | const stream = skip(1, to3); 30 | 31 | for await (const item of stream) { 32 | t.equal(item, i, `should see the item ${i}`); 33 | i++; 34 | } 35 | 36 | t.equal(i - 1, 2, 'should have seen 2 iterations'); 37 | i = 1; 38 | 39 | for await (const item of stream) { 40 | t.equal(item, i, `should see the item ${i} again`); 41 | i++; 42 | } 43 | 44 | t.equal(i - 1, 2, 'should have seen 2 iterations again'); 45 | 46 | }); 47 | 48 | t.test('forward control flow event', async t => { 49 | let i = 0; 50 | const to3 = breakableCounter(); 51 | const stream = skip(1, to3); 52 | for await (const item of stream) { 53 | i++; 54 | break; 55 | } 56 | 57 | t.equal(i, 1, 'should have done a single iteration'); 58 | 59 | t.equal(to3.done, true, 'should have called the return of underlying iterator'); 60 | t.equal(to3.index, 2, 'should have pulled two items (one for the skipped'); 61 | }); 62 | 63 | t.test('curried skip', async t => { 64 | const skipOne = skip(1); 65 | let i = 1; 66 | for await (const item of skipOne(counterGenerator())) { 67 | t.equal(item, i, `should have seen the item ${i}`); 68 | i++; 69 | } 70 | 71 | i = 1; 72 | for await (const item of skipOne(counterGenerator())) { 73 | t.equal(item, i, `should have seen the item ${i}`); 74 | i++; 75 | } 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/slice.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {slice} from '../src/lib/operators'; 3 | import {breakableCounter, counterGenerator, counterIterable} from './util/source.js'; 4 | 5 | export default test('slice operator test suite', t => { 6 | 7 | t.test('basic: all arguments', async t => { 8 | let i = 0; 9 | const to5 = counterGenerator(5); 10 | const stream = slice(1, 4, to5); 11 | 12 | const result = []; 13 | for await (const item of stream) { 14 | i++; 15 | result.push(item); 16 | } 17 | 18 | t.equal(i, 3, 'should have seen 3 iterations'); 19 | t.deepEqual(result, [1, 2, 3], 'should have seen the items'); 20 | }); 21 | 22 | t.test('basic: with no end', async t => { 23 | let i = 0; 24 | const to5 = counterGenerator(5); 25 | const stream = slice(1, to5); 26 | 27 | const result = []; 28 | for await (const item of stream) { 29 | i++; 30 | result.push(item); 31 | } 32 | 33 | t.equal(i, 4, 'should have seen 4 iterations'); 34 | t.deepEqual(result, [1, 2, 3, 4], 'should have gone to the end of the stream'); 35 | }); 36 | 37 | t.test('basic: with no end and no start', async t => { 38 | let i = 0; 39 | const to5 = counterGenerator(5); 40 | const stream = slice(to5); 41 | 42 | const result = []; 43 | for await (const item of stream) { 44 | i++; 45 | result.push(item); 46 | } 47 | 48 | t.equal(i, 5, 'should have seen 5 iterations'); 49 | t.deepEqual(result, [0, 1, 2, 3, 4], 'should have seen all the items'); 50 | }); 51 | 52 | t.test('curry with start and end', async t => { 53 | const sl = slice(1, 4); 54 | const result = []; 55 | let i = 0; 56 | for await (const item of sl(counterGenerator(5))) { 57 | i++; 58 | result.push(item); 59 | } 60 | 61 | t.equal(i, 3, 'should have seen 3 iterations'); 62 | t.deepEqual(result, [1, 2, 3], 'should have seen the items'); 63 | }); 64 | 65 | t.test('curry with only start', async t => { 66 | let i = 0; 67 | const to5 = counterGenerator(5); 68 | const sl = slice(1); 69 | 70 | const result = []; 71 | for await (const item of sl(to5)) { 72 | i++; 73 | result.push(item); 74 | } 75 | 76 | t.equal(i, 4, 'should have seen 4 iterations'); 77 | t.deepEqual(result, [1, 2, 3, 4], 'should have gone to the end of the stream'); 78 | }); 79 | 80 | t.test('curry with no start and with no end', async t => { 81 | let i = 0; 82 | const to5 = counterGenerator(5); 83 | const sl = slice(); 84 | 85 | const result = []; 86 | for await (const item of sl(to5)) { 87 | i++; 88 | result.push(item); 89 | } 90 | 91 | t.equal(i, 5, 'should have seen 5 iterations'); 92 | t.deepEqual(result, [0, 1, 2, 3, 4], 'should have seen all the items'); 93 | }); 94 | 95 | t.test('forward the consumable nature of the underlying asyncIterable: gen', async t => { 96 | const to5 = counterGenerator(5); 97 | let i = 0; 98 | let stream = slice(1, 4, to5); 99 | let result = []; 100 | 101 | for await (const item of stream) { 102 | result.push(item); 103 | i++; 104 | } 105 | 106 | for await (const item of stream) { 107 | result.push(item); 108 | i++; 109 | } 110 | 111 | t.equal(i, 3, 'should have seen 3 iterations only'); 112 | t.deepEqual(result, [1, 2, 3]); 113 | }); 114 | 115 | t.test('forward the consumable nature of the underlying asyncIterable: stateless iterator', async t => { 116 | const to5 = counterIterable(5); 117 | let i = 0; 118 | let stream = slice(1, 4, to5); 119 | let result = []; 120 | 121 | for await (const item of stream) { 122 | result.push(item); 123 | i++; 124 | } 125 | 126 | for await (const item of stream) { 127 | result.push(item); 128 | i++; 129 | } 130 | 131 | t.equal(i, 6, 'should have seen 6 iterations'); 132 | t.deepEqual(result, [1, 2, 3, 1, 2, 3]); 133 | }); 134 | 135 | t.test('should forward control flow events', async t => { 136 | const to5 = breakableCounter(5); 137 | const stream = slice(1, 4, to5); 138 | let i = 0; 139 | for await (const item of stream) { 140 | i++; 141 | break; 142 | } 143 | 144 | t.equal(i, 1, 'should have seen 1 iteration only'); 145 | t.equal(to5.done, true, 'should have called the return hook of the underlying stream'); 146 | t.equal(to5.index, 2, 'should have pulled two items (one skipped)'); 147 | }); 148 | 149 | }); 150 | -------------------------------------------------------------------------------- /test/some.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {counterGenerator} from './util/source.js'; 3 | import {some} from '../src/lib/operators'; 4 | 5 | export default test('some test suite', t => { 6 | t.test('basic', async t => { 7 | let i = 0; 8 | const lowerThan5 = await some((item, index) => { 9 | t.equal(i, index, 'should have forwarded the index'); 10 | i++; 11 | return item <= 5; 12 | }, counterGenerator(5)); 13 | 14 | t.equal(i, 1, 'should have done 1 iteration as the first item is lower than 5'); 15 | t.equal(lowerThan5, true, 'at least one item is lower than 5'); 16 | 17 | i = 0; 18 | 19 | const greaterThanEvil = await some((item, index) => { 20 | t.equal(i, index, 'should have forwarded the index'); 21 | i++; 22 | return item >= 666; 23 | }, counterGenerator(5)); 24 | 25 | t.equal(i, 5, 'should have done the 5 iterations'); 26 | t.equal(greaterThanEvil, false, 'There is no item greater than 666'); 27 | }); 28 | 29 | t.test('curried', async t => { 30 | let i = 0; 31 | const lowerThan5 = await some((item, index) => { 32 | t.equal(i, index, 'should have forwarded the index'); 33 | i++; 34 | return item <= 5; 35 | })(counterGenerator(5)); 36 | 37 | t.equal(i, 1, 'should have done 1 iteration as the first item is lower than 5'); 38 | t.equal(lowerThan5, true, 'at least one item is lower than 5'); 39 | 40 | i = 0; 41 | 42 | const greaterThanEvil = await some((item, index) => { 43 | t.equal(i, index, 'should have forwarded the index'); 44 | i++; 45 | return item >= 666; 46 | })(counterGenerator(5)); 47 | 48 | t.equal(i, 5, 'should have done the 5 iterations'); 49 | t.equal(greaterThanEvil, false, 'There is no item greater than 666'); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /test/stream.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {proto, stream, toAsync} from '../src/lib/stream'; 3 | import {counterGenerator, counterIterable} from './util/source.js'; 4 | 5 | export default test('stream test suite', t => { 6 | t.test('from array', async t => { 7 | let iter = 0; 8 | const s = toAsync([0, 1, 2]); 9 | t.equal(typeof s[Symbol.asyncIterator], 'function', 'should be an async iterator'); 10 | for await (const i of s) { 11 | t.equal(i, iter, `should see the value ${iter}`); 12 | iter++; 13 | } 14 | 15 | t.equal(iter, 3, 'should have seen 3 iterations'); 16 | }); 17 | 18 | t.test('from array: consume few times', async t => { 19 | let iter = 0; 20 | const s = toAsync([0, 1, 2]); 21 | for await (const i of s) { 22 | t.equal(i, iter, `should see the value ${iter}`); 23 | iter++; 24 | } 25 | t.equal(iter, 3, 'should have seen 3 iterations'); 26 | iter = 0; 27 | for await (const i of s) { 28 | t.equal(i, iter, `should see the value ${iter} again`); 29 | iter++; 30 | } 31 | t.equal(iter, 3, 'should have seen 3 iterations again'); 32 | }); 33 | 34 | t.test('from generator', async t => { 35 | const gen = function* () { 36 | for (let i = 0; i < 3; i++) { 37 | yield i; 38 | } 39 | }; 40 | let iter = 0; 41 | const iterable = gen(); 42 | const s = toAsync(iterable); 43 | 44 | t.equal(typeof s[Symbol.asyncIterator], 'function', 'should be an async iterator'); 45 | 46 | for await (const i of s) { 47 | t.equal(i, iter, `should see the value ${iter}`); 48 | iter++; 49 | } 50 | 51 | for await (const i of s) { 52 | iter++; 53 | } 54 | 55 | t.equal(iter, 3, 'should have seen 3 iterations only'); 56 | }); 57 | 58 | t.test('from [Symbol.iterator]', async t => { 59 | const gen = function* () { 60 | for (let i = 0; i < 3; i++) { 61 | yield i; 62 | } 63 | }; 64 | 65 | const iterable = { 66 | [Symbol.iterator]: gen 67 | }; 68 | let iter = 0; 69 | const s = toAsync(iterable); 70 | 71 | t.equal(typeof s[Symbol.asyncIterator], 'function', 'should be an async iterator'); 72 | 73 | for await (const i of s) { 74 | t.equal(i, iter, `should see the value ${iter}`); 75 | iter++; 76 | } 77 | 78 | for await (const i of s) { 79 | iter++; 80 | } 81 | 82 | t.equal(iter, 6, 'should have consumed the iterable two times'); 83 | }); 84 | 85 | t.test('from an asyncIterable', async t => { 86 | const s = toAsync(counterGenerator()); 87 | let iter = 0; 88 | t.equal(typeof s[Symbol.asyncIterator], 'function', 'should be an async iterator'); 89 | 90 | for await (const i of s) { 91 | t.equal(i, iter, `should see the value ${iter}`); 92 | iter++; 93 | } 94 | 95 | for await (const i of s) { 96 | iter++; 97 | } 98 | t.equal(iter, 3, 'should have seen 3 iterations only'); 99 | }); 100 | 101 | t.test('from an asyncIterable: consume two times', async t => { 102 | let iter = 0; 103 | const s = toAsync(counterIterable()); 104 | 105 | t.equal(typeof s[Symbol.asyncIterator], 'function', 'should be an async iterator'); 106 | 107 | for await (const i of s) { 108 | t.equal(i, iter, `should see the value ${iter}`); 109 | iter++; 110 | } 111 | 112 | for await (const i of s) { 113 | iter++; 114 | } 115 | t.equal(iter, 6, 'should have seen 6 iterations'); 116 | }); 117 | 118 | t.test('stream factory: should have the stream proto', t => { 119 | const s = stream(counterGenerator()); 120 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 121 | }); 122 | 123 | t.test('stream factory: map', async t => { 124 | const s = stream(counterGenerator()) 125 | .map(i => i * i); 126 | 127 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 128 | 129 | let i = 0; 130 | for await (const item of s) { 131 | t.equal(item, i * i, `should have seen ${i * i} the square of ${i}`); 132 | i++; 133 | } 134 | t.equal(i, 3, 'should have seen 3 iterations'); 135 | }); 136 | 137 | t.test('stream factory: filter', async t => { 138 | const s = stream(counterGenerator()) 139 | .filter(i => i % 2 === 0); 140 | 141 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 142 | 143 | const result = []; 144 | for await (const item of s) { 145 | result.push(item); 146 | } 147 | t.deepEqual(result, [0, 2], 'should have seen the odd numbers only'); 148 | t.equal(result.length, 2, 'should have seen 2 iterations'); 149 | }); 150 | 151 | t.test('stream factory: filterMap', async t => { 152 | const s = stream([counterGenerator(), counterGenerator(5)]) 153 | .flatMap(i => i * i); 154 | 155 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 156 | 157 | const result = []; 158 | for await (const item of s) { 159 | result.push(item); 160 | } 161 | t.deepEqual(result, [0, 1, 4, 0, 1, 4, 9, 16], 'should have seen the flattened squared numbers'); 162 | t.equal(result.length, 8, 'should have seen 8 iterations'); 163 | }); 164 | 165 | t.test('stream factory: slice (one argument)', async t => { 166 | const s = stream(counterGenerator(5)) 167 | .slice(2); 168 | 169 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 170 | 171 | const result = []; 172 | for await (const item of s) { 173 | result.push(item); 174 | } 175 | t.deepEqual(result, [2, 3, 4], 'should have seen starting from third item to the end'); 176 | t.equal(result.length, 3, 'should have seen 3 iterations'); 177 | }); 178 | 179 | t.test('stream factory: slice (two arguments)', async t => { 180 | const s = stream(counterGenerator(5)) 181 | .slice(2, 3); 182 | 183 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 184 | 185 | const result = []; 186 | for await (const item of s) { 187 | result.push(item); 188 | } 189 | t.deepEqual(result, [2], 'should have seen starting from third item ending to the third'); 190 | t.equal(result.length, 1, 'should have seen 1 iteration'); 191 | }); 192 | 193 | t.test('stream factory: concat', async t => { 194 | const s = stream(counterGenerator()) 195 | .concat(counterGenerator(), 666, [1, 2], counterGenerator()); 196 | 197 | t.equal(Object.getPrototypeOf(s), proto, 'should have the stream prototype'); 198 | 199 | const result = []; 200 | for await (const item of s) { 201 | result.push(item); 202 | } 203 | t.deepEqual(result, [0, 1, 2, 0, 1, 2, 666, [1, 2], 0, 1, 2], 'should have seen the concatenated stream'); 204 | t.equal(result.length, 11, 'should have seen 11 iterations'); 205 | }); 206 | 207 | t.test('stream factory reduce', async t => { 208 | const s = stream(counterGenerator(5)); 209 | const value = await s.reduce((curr, acc) => curr + acc, 0); 210 | t.equal(value, 10, 'should see the sum of 5 first integers'); 211 | }); 212 | 213 | t.test('stream factory find', async t => { 214 | const s = stream(counterGenerator(10)); 215 | const value = await s.find(i => i > 5 && i % 2 === 0); 216 | t.equal(value, 6, 'should return the first odd integer greater than 5'); 217 | 218 | const notFound = await stream(counterGenerator()).find(i => i > 666); 219 | t.equal(notFound, undefined, 'should return undefined when not found'); 220 | }); 221 | 222 | t.test('stream factory findIndex', async t => { 223 | const s = stream(counterGenerator(10)).map(i => i * i); 224 | const value = await s.findIndex(i => i > 5 && i % 2 === 0); 225 | t.equal(value, 4, 'should return the index of the first odd squared integer greater than 5'); 226 | const notFound = await stream(counterGenerator()).findIndex(i => i > 666); 227 | t.equal(notFound, -1, 'should return undefined when not found'); 228 | 229 | }); 230 | 231 | t.test('stream factory includes', async t => { 232 | const doesnotInclude = await stream(counterGenerator()).includes(666); 233 | t.equal(doesnotInclude, false); 234 | const includes = await stream(counterGenerator()).includes(2); 235 | t.equal(includes, true); 236 | }); 237 | 238 | t.test('stream factory every', async t => { 239 | const hasEvery = await stream(counterGenerator()).every(i => i < 5); 240 | t.equal(hasEvery, true, 'every single item is lower than 5'); 241 | const hasNotEvery = await stream(counterGenerator()).every(i => i % 2 === 0); 242 | t.equal(hasNotEvery, false, 'not every item is odd'); 243 | }); 244 | 245 | t.test('stream factory some', async t => { 246 | const some = await stream(counterGenerator()).some(i => i % 2 === 0); 247 | t.equal(some, true, 'some item is odd'); 248 | const hasNot = await stream(counterGenerator()).some(i => i > 4); 249 | t.equal(hasNot, false, 'none item is greater than 4'); 250 | }); 251 | }); 252 | -------------------------------------------------------------------------------- /test/take.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {take} from '../src/lib/operators'; 3 | import {breakableCounter, counterGenerator, counterIterable} from './util/source'; 4 | 5 | export default test('take operator', t => { 6 | 7 | t.test('basic', async t => { 8 | let i = 0; 9 | const to3 = counterGenerator(); 10 | const stream = take(2, to3); 11 | 12 | for await (const item of stream) { 13 | t.equal(item, i, `should see item ${i}`); 14 | i++; 15 | } 16 | 17 | for await (const item of stream) { 18 | i++; 19 | } 20 | 21 | t.equal(i, 2, 'should have seen 2 iterations only'); 22 | 23 | }); 24 | 25 | t.test('forward the consumable nature of the underlying asyncIterator', async t => { 26 | let i = 0; 27 | const to3 = counterIterable(); 28 | const stream = take(2, to3); 29 | 30 | for await (const item of stream) { 31 | t.equal(item, i, `should see the item ${i}`); 32 | i++; 33 | } 34 | 35 | t.equal(i, 2, 'should have seen 2 iterations'); 36 | i = 0; 37 | 38 | for await (const item of stream) { 39 | t.equal(item, i, `should see the item ${i} again`); 40 | i++; 41 | } 42 | 43 | t.equal(i, 2, 'should have seen 2 iterations again'); 44 | 45 | }); 46 | 47 | t.test('forward control flow event', async t => { 48 | let i = 0; 49 | const to3 = breakableCounter(); 50 | const stream = take(2, to3); 51 | for await (const item of stream) { 52 | i++; 53 | break; 54 | } 55 | 56 | t.equal(i, 1, 'should have done a single iteration'); 57 | 58 | t.equal(to3.done, true, 'should have called the return of underlying iterator'); 59 | t.equal(to3.index, 1, 'should have pulled one single time'); 60 | }); 61 | 62 | t.test('curried take', async t => { 63 | const getTwo = take(2); 64 | let i = 0; 65 | for await (const item of getTwo(counterGenerator())) { 66 | t.equal(item, i, `should have seen the item ${i}`); 67 | i++; 68 | } 69 | 70 | i = 0; 71 | for await (const item of getTwo(counterGenerator())) { 72 | t.equal(item, i, `should have seen the item ${i}`); 73 | i++; 74 | } 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/util/source.js: -------------------------------------------------------------------------------- 1 | export const wait = (time = 10) => new Promise(resolve => setTimeout(() => resolve(), time)); 2 | 3 | export const counterGenerator = async function* (limit = 3) { 4 | let i = 0; 5 | while (true) { 6 | if (i >= limit) { 7 | break; 8 | } 9 | await wait(); 10 | yield i; 11 | i++; 12 | } 13 | }; 14 | 15 | export const counterIterable = (limit = 3) => ({ 16 | [Symbol.asyncIterator]() { 17 | return counterGenerator(limit); 18 | } 19 | }); 20 | 21 | 22 | export const breakableCounter = (limit = 3) => { 23 | let i = 0; 24 | let done = false; 25 | 26 | const instance = { 27 | [Symbol.asyncIterator]() { 28 | return this; 29 | }, 30 | async next() { 31 | if (i >= limit || this.done === true) { 32 | return {done: true}; 33 | } 34 | const item = {value: i, done: false}; 35 | i++; 36 | return item; 37 | }, 38 | 39 | async return() { 40 | done = true; 41 | return {done: true}; 42 | } 43 | }; 44 | 45 | Object.defineProperties(instance, { 46 | index: { 47 | get() { 48 | return i; 49 | } 50 | }, 51 | done: { 52 | get() { 53 | return done; 54 | } 55 | } 56 | }); 57 | 58 | return instance; 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import {test} from 'zora'; 2 | import {curry} from '../src/lib/util'; 3 | 4 | export default test('utils test suite', t => { 5 | t.test('should curry function with two arguments', t => { 6 | const sum = curry((a, b) => a + b); 7 | const plusTwo = sum(2); 8 | 9 | t.equal(sum(3, 2), 5, 'should return result when called with two arguments'); 10 | t.equal(typeof plusTwo, 'function', 'should return a function when called with one argument'); 11 | t.equal(plusTwo(4), 6, 'should curry function if called with one argument'); 12 | }); 13 | }); 14 | --------------------------------------------------------------------------------