forked from cujojs/most
-
Notifications
You must be signed in to change notification settings - Fork 0
/
most.js
637 lines (524 loc) · 20 KB
/
most.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
/** @license MIT License (c) copyright 2010-2015 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
var Stream = require('./lib/Stream');
var base = require('./lib/base');
var core = require('./lib/source/core');
var from = require('./lib/source/from').from;
var periodic = require('./lib/source/periodic').periodic;
/**
* Core stream type
* @type {Stream}
*/
exports.Stream = Stream;
// Add of and empty to constructor for fantasy-land compat
exports.of = Stream.of = core.of;
exports.empty = Stream.empty = core.empty;
exports.never = core.never;
exports.from = from;
exports.periodic = periodic;
//-----------------------------------------------------------------------
// Creating
var create = require('./lib/source/create');
/**
* Create a stream by imperatively pushing events.
* @param {function(add:function(x), end:function(e)):function} run function
* that will receive 2 functions as arguments, the first to add new values to the
* stream and the second to end the stream. It may *return* a function that
* will be called once all consumers have stopped observing the stream.
* @returns {Stream} stream containing all events added by run before end
*/
exports.create = create.create;
//-----------------------------------------------------------------------
// Adapting other sources
var events = require('./lib/source/fromEvent');
/**
* Create a stream of events from the supplied EventTarget or EventEmitter
* @param {String} event event name
* @param {EventTarget|EventEmitter} source EventTarget or EventEmitter. The source
* must support either addEventListener/removeEventListener (w3c EventTarget:
* http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget),
* or addListener/removeListener (node EventEmitter: http://nodejs.org/api/events.html)
* @returns {Stream} stream of events of the specified type from the source
*/
exports.fromEvent = events.fromEvent;
exports.fromEventWhere = events.fromEventWhere;
//-----------------------------------------------------------------------
// Lifting functions
var lift = require('./lib/combinator/lift').lift;
/**
* Lift a function that accepts values and returns a value, and return a function
* that accepts streams and returns a stream.
* @type {function(f:function(...args):*):function(...streams):Stream<*>}
*/
exports.lift = lift;
//-----------------------------------------------------------------------
// Observing
var observe = require('./lib/combinator/observe');
exports.observe = observe.observe;
exports.forEach = observe.observe;
exports.drain = observe.drain;
/**
* Process all the events in the stream
* @returns {Promise} promise that fulfills when the stream ends, or rejects
* if the stream fails with an unhandled error.
*/
Stream.prototype.observe = Stream.prototype.forEach = function(f) {
return observe.observe(f, this);
};
/**
* Consume all events in the stream, without providing a function to process each.
* This causes a stream to become active and begin emitting events, and is useful
* in cases where all processing has been setup upstream via other combinators, and
* there is no need to process the terminal events.
* @returns {Promise} promise that fulfills when the stream ends, or rejects
* if the stream fails with an unhandled error.
*/
Stream.prototype.drain = function() {
return observe.drain(this);
};
//-------------------------------------------------------
var loop = require('./lib/combinator/loop').loop;
exports.loop = loop;
/**
* Generalized feedback loop. Call a stepper function for each event. The stepper
* will be called with 2 params: the current seed and the an event value. It must
* return a new { seed, value } pair. The `seed` will be fed back into the next
* invocation of stepper, and the `value` will be propagated as the event value.
* @param {function(seed:*, value:*):{seed:*, value:*}} stepper loop step function
* @param {*} seed initial seed value passed to first stepper call
* @returns {Stream} new stream whose values are the `value` field of the objects
* returned by the stepper
*/
Stream.prototype.loop = function(stepper, seed) {
return loop(stepper, seed, this);
};
//-------------------------------------------------------
var accumulate = require('./lib/combinator/accumulate');
exports.scan = accumulate.scan;
exports.reduce = accumulate.reduce;
/**
* Create a stream containing successive reduce results of applying f to
* the previous reduce result and the current stream item.
* @param {function(result:*, x:*):*} f reducer function
* @param {*} initial initial value
* @returns {Stream} new stream containing successive reduce results
*/
Stream.prototype.scan = function(f, initial) {
return accumulate.scan(f, initial, this);
};
/**
* Reduce the stream to produce a single result. Note that reducing an infinite
* stream will return a Promise that never fulfills, but that may reject if an error
* occurs.
* @param {function(result:*, x:*):*} f reducer function
* @param {*} initial optional initial value
* @returns {Promise} promise for the file result of the reduce
*/
Stream.prototype.reduce = function(f, initial) {
return accumulate.reduce(f, initial, this);
};
//-----------------------------------------------------------------------
// Building and extending
var unfold = require('./lib/source/unfold');
var iterate = require('./lib/source/iterate');
var generate = require('./lib/source/generate');
var build = require('./lib/combinator/build');
exports.unfold = unfold.unfold;
exports.iterate = iterate.iterate;
exports.generate = generate.generate;
exports.repeat = iterate.repeat;
exports.concat = build.cycle;
exports.concat = build.concat;
exports.startWith = build.cons;
/**
* Tie this stream into a circle, thus creating an infinite stream
* @returns {Stream} new infinite stream
*/
Stream.prototype.cycle = function() {
return build.cycle(this);
};
/**
* @param {Stream} tail
* @returns {Stream} new stream containing all items in this followed by
* all items in tail
*/
Stream.prototype.concat = function(tail) {
return build.concat(this, tail);
};
/**
* @param {*} x value to prepend
* @returns {Stream} a new stream with x prepended
*/
Stream.prototype.startWith = function(x) {
return build.cons(x, this);
};
//-----------------------------------------------------------------------
// Transforming
var transform = require('./lib/combinator/transform');
exports.map = transform.map;
exports.ap = transform.ap;
exports.constant = transform.constant;
exports.tap = transform.tap;
/**
* Transform each value in the stream by applying f to each
* @param {function(*):*} f mapping function
* @returns {Stream} stream containing items transformed by f
*/
Stream.prototype.map = function(f) {
return transform.map(f, this);
};
/**
* Assume this stream contains functions, and apply each function to each item
* in the provided stream. This generates, in effect, a cross product.
* @param {Stream} xs stream of items to which
* @returns {Stream} stream containing the cross product of items
*/
Stream.prototype.ap = function(xs) {
return transform.ap(this, xs);
};
/**
* Replace each value in the stream with x
* @param {*} x
* @returns {Stream} stream containing items replaced with x
*/
Stream.prototype.constant = function(x) {
return transform.constant(x, this);
};
/**
* Perform a side effect for each item in the stream
* @param {function(x:*):*} f side effect to execute for each item. The
* return value will be discarded.
* @returns {Stream} new stream containing the same items as this stream
*/
Stream.prototype.tap = function(f) {
return transform.tap(f, this);
};
//-----------------------------------------------------------------------
// FlatMapping
var flatMap = require('./lib/combinator/flatMap');
exports.flatMap = exports.chain = flatMap.flatMap;
exports.join = flatMap.join;
/**
* Map each value in the stream to a new stream, and merge it into the
* returned outer stream. Event arrival times are preserved.
* @param {function(x:*):Stream} f chaining function, must return a Stream
* @returns {Stream} new stream containing all events from each stream returned by f
*/
Stream.prototype.flatMap = Stream.prototype.chain = function(f) {
return flatMap.flatMap(f, this);
};
/**
* Monadic join. Flatten a Stream<Stream<X>> to Stream<X> by merging inner
* streams to the outer. Event arrival times are preserved.
* @returns {Stream<X>} new stream containing all events of all inner streams
*/
Stream.prototype.join = function() {
return flatMap.join(this);
};
var flatMapEnd = require('./lib/combinator/flatMapEnd').flatMapEnd;
exports.flatMapEnd = flatMapEnd;
/**
* Map the end event to a new stream, and begin emitting its values.
* @param {function(x:*):Stream} f function that receives the end event value,
* and *must* return a new Stream to continue with.
* @returns {Stream} new stream that emits all events from the original stream,
* followed by all events from the stream returned by f.
*/
Stream.prototype.flatMapEnd = function(f) {
return flatMapEnd(f, this);
};
var concatMap = require('./lib/combinator/concatMap').concatMap;
exports.concatMap = concatMap;
Stream.prototype.concatMap = function(f) {
return concatMap(f, this);
};
//-----------------------------------------------------------------------
// Merging
var merge = require('./lib/combinator/merge');
exports.merge = merge.merge;
/**
* Merge this stream and all the provided streams
* @returns {Stream} stream containing items from this stream and s in time
* order. If two events are simultaneous they will be merged in
* arbitrary order.
*/
Stream.prototype.merge = function(/*...streams*/) {
return merge.mergeArray(base.cons(this, arguments));
};
//-----------------------------------------------------------------------
// Combining
var combine = require('./lib/combinator/combine');
exports.combine = combine.combine;
/**
* Combine latest events from all input streams
* @param {function(...events):*} f function to combine most recent events
* @returns {Stream} stream containing the result of applying f to the most recent
* event of each input stream, whenever a new event arrives on any stream.
*/
Stream.prototype.combine = function(f /*, ...streams*/) {
return combine.combineArray(f, base.replace(this, 0, arguments));
};
//-----------------------------------------------------------------------
// Sampling
var sample = require('./lib/combinator/sample');
exports.sample = sample.sample;
exports.sampleWith = sample.sampleWith;
/**
* When an event arrives on sampler, emit the latest event value from stream.
* @param {Stream} sampler stream of events at whose arrival time
* signal's latest value will be propagated
* @returns {Stream} sampled stream of values
*/
Stream.prototype.sampleWith = function(sampler) {
return sample.sampleWith(sampler, this);
};
/**
* When an event arrives on this stream, emit the result of calling f with the latest
* values of all streams being sampled
* @param {function(...values):*} f function to apply to each set of sampled values
* @returns {Stream} stream of sampled and transformed values
*/
Stream.prototype.sample = function(f /* ...streams */) {
return sample.sampleArray(f, this, base.tail(arguments));
};
//-----------------------------------------------------------------------
// Zipping
var zip = require('./lib/combinator/zip');
exports.zip = zip.zip;
/**
* Pair-wise combine items with those in s. Given 2 streams:
* [1,2,3] zipWith f [4,5,6] -> [f(1,4),f(2,5),f(3,6)]
* Note: zip causes fast streams to buffer and wait for slow streams.
* @param {function(a:Stream, b:Stream, ...):*} f function to combine items
* @returns {Stream} new stream containing pairs
*/
Stream.prototype.zip = function(f /*, ...streams*/) {
return zip.zipArray(f, base.replace(this, 0, arguments));
};
//-----------------------------------------------------------------------
// Switching
var switchLatest = require('./lib/combinator/switch').switch;
exports.switch = switchLatest;
exports.switchLatest = switchLatest;
/**
* Given a stream of streams, return a new stream that adopts the behavior
* of the most recent inner stream.
* @returns {Stream} switching stream
*/
Stream.prototype.switch = Stream.prototype.switchLatest = function() {
return switchLatest(this);
};
//-----------------------------------------------------------------------
// Filtering
var filter = require('./lib/combinator/filter');
exports.filter = filter.filter;
exports.distinct = filter.distinct;
exports.distinctBy = filter.distinctBy;
/**
* Retain only items matching a predicate
* stream: -12345678-
* filter(x => x % 2 === 0, stream): --2-4-6-8-
* @param {function(x:*):boolean} p filtering predicate called for each item
* @returns {Stream} stream containing only items for which predicate returns truthy
*/
Stream.prototype.filter = function(p) {
return filter.filter(p, this);
};
/**
* Remove adjacent duplicates, using === to compare items
* stream: -abbcd-
* distinct(stream): -ab-cd-
* @returns {Stream} stream with no adjacent duplicates
*/
Stream.prototype.distinct = function() {
return filter.distinct(this);
};
/**
* Remove adjacent duplicates, using supplied equals function to compare items
* @param {function(a:*, b:*):boolean} equals function to compare items.
* @returns {Stream} stream with no adjacent duplicates
*/
Stream.prototype.distinctBy = function(equals) {
return filter.distinctBy(equals, this);
};
//-----------------------------------------------------------------------
// Slicing
var slice = require('./lib/combinator/slice');
exports.take = slice.take;
exports.skip = slice.skip;
exports.slice = slice.slice;
exports.takeWhile = slice.takeWhile;
exports.skipWhile = slice.skipWhile;
/**
* stream: -abcd-
* take(2, stream): -ab|
* @param {Number} n take up to this many events
* @returns {Stream} stream containing at most the first n items from this stream
*/
Stream.prototype.take = function(n) {
return slice.take(n, this);
};
/**
* stream: -abcd->
* skip(2, stream): ---cd->
* @param {Number} n skip this many events
* @returns {Stream} stream not containing the first n events
*/
Stream.prototype.skip = function(n) {
return slice.skip(n, this);
};
/**
* Slice a stream by event index. Equivalent to, but more efficient than
* stream.take(end).skip(start);
* NOTE: Negative start and end are not supported
* @param {Number} start skip all events before the start index
* @param {Number} end allow all events from the start index to the end index
* @returns {Stream} stream containing items where start <= index < end
*/
Stream.prototype.slice = function(start, end) {
return slice.slice(start, end, this);
};
/**
* stream: -123451234->
* takeWhile(x => x < 5, stream): -1234|
* @param {function(x:*):boolean} p predicate
* @returns {Stream} stream containing items up to, but not including, the
* first item for which p returns falsy.
*/
Stream.prototype.takeWhile = function(p) {
return slice.takeWhile(p, this);
};
/**
* stream: -123451234->
* skipWhile(x => x < 5, stream): -----51234->
* @param {function(x:*):boolean} p predicate
* @returns {Stream} stream containing items following *and including* the
* first item for which p returns falsy.
*/
Stream.prototype.skipWhile = function(p) {
return slice.skipWhile(p, this);
};
//-----------------------------------------------------------------------
// Time slicing
var timeslice = require('./lib/combinator/timeslice');
exports.until = exports.takeUntil = timeslice.takeUntil;
exports.since = exports.skipUntil = timeslice.skipUntil;
exports.during = timeslice.during; // EXPERIMENTAL
/**
* stream: -a-b-c-d-e-f-g->
* signal: -------x
* takeUntil(signal, stream): -a-b-c-|
* @param {Stream} signal retain only events in stream before the first
* event in signal
* @returns {Stream} new stream containing only events that occur before
* the first event in signal.
*/
Stream.prototype.until = Stream.prototype.takeUntil = function(signal) {
return timeslice.takeUntil(signal, this);
};
/**
* stream: -a-b-c-d-e-f-g->
* signal: -------x
* takeUntil(signal, stream): -------d-e-f-g->
* @param {Stream} signal retain only events in stream at or after the first
* event in signal
* @returns {Stream} new stream containing only events that occur after
* the first event in signal.
*/
Stream.prototype.since = Stream.prototype.skipUntil = function(signal) {
return timeslice.skipUntil(signal, this);
};
/**
* **EXPERIMENTAL**
* stream: -a-b-c-d-e-f-g->
* timeWindow: -----s
* s: -----t
* stream.during(timeWindow): -----c-d-e-|
* @param {Stream<Stream>} timeWindow a stream whose first event (s) represents
* the window start time. That event (s) is itself a stream whose first event (t)
* represents the window end time
* @returns {Stream} new stream containing only events within the provided timespan
*/
Stream.prototype.during = function(timeWindow) {
return timeslice.within(timeWindow, this);
};
//-----------------------------------------------------------------------
// Delaying
var delay = require('./lib/combinator/delay').delay;
exports.delay = delay;
/**
* @param {Number} delayTime milliseconds to delay each item
* @returns {Stream} new stream containing the same items, but delayed by ms
*/
Stream.prototype.delay = function(delayTime) {
return delay(delayTime, this);
};
//-----------------------------------------------------------------------
// Getting event timestamp
var timestamp = require('./lib/combinator/timestamp').timestamp;
exports.timestamp = timestamp;
/**
* Expose event timestamps into the stream. Turns a Stream<X> into
* Stream<{time:t, value:X}>
* @returns {Stream<{time:number, value:*}>}
*/
Stream.prototype.timestamp = function() {
return timestamp(this);
};
//-----------------------------------------------------------------------
// Rate limiting
var limit = require('./lib/combinator/limit');
exports.throttle = limit.throttle;
exports.debounce = limit.debounce;
/**
* Limit the rate of events
* stream: abcd----abcd----
* throttle(2, stream): a-c-----a-c-----
* @param {Number} period time to suppress events
* @returns {Stream} new stream that skips events for throttle period
*/
Stream.prototype.throttle = function(period) {
return limit.throttle(period, this);
};
/**
* Wait for a burst of events to subside and emit only the last event in the burst
* stream: abcd----abcd----
* debounce(2, stream): -----d-------d--
* @param {Number} period events occuring more frequently than this
* on the provided scheduler will be suppressed
* @returns {Stream} new debounced stream
*/
Stream.prototype.debounce = function(period) {
return limit.debounce(period, this);
};
//-----------------------------------------------------------------------
// Awaiting Promises
var promises = require('./lib/combinator/promises');
exports.fromPromise = promises.fromPromise;
exports.await = promises.await;
/**
* Await promises, turning a Stream<Promise<X>> into Stream<X>. Preserves
* event order, but timeshifts events based on promise resolution time.
* @returns {Stream<X>} stream containing non-promise values
*/
Stream.prototype.await = function() {
return promises.await(this);
};
//-----------------------------------------------------------------------
// Error handling
var errors = require('./lib/combinator/errors');
exports.flatMapError = errors.flatMapError;
exports.throwError = errors.throwError;
/**
* If this stream encounters an error, recover and continue with items from stream
* returned by f.
* stream: -a-b-c-X-
* f(X): d-e-f-g-
* flatMapError(f, stream): -a-b-c-d-e-f-g-
* @param {function(error:*):Stream} f function which returns a new stream
* @returns {Stream} new stream which will recover from an error by calling f
*/
Stream.prototype.flatMapError = function(f) {
return errors.flatMapError(f, this);
};