Skip to content

Commit 3a69934

Browse files
committed
[add] fromEvent() & related interfaces
[add] toPromise() utility
1 parent 52a03d1 commit 3a69934

File tree

5 files changed

+151
-5
lines changed

5 files changed

+151
-5
lines changed

.npmignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.editorconfig
2+
test/
3+
.rts2_cache_*/
4+
docs/
5+
.travis.yml

.travis.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
branches:
2+
only:
3+
- master
4+
5+
language: node_js
6+
node_js:
7+
- lts/*
8+
cache:
9+
directories:
10+
- node_modules
11+
12+
install:
13+
- npm install
14+
script:
15+
- npm run ${SCRIPT}
16+
deploy:
17+
provider: pages
18+
on:
19+
branch: master
20+
skip_cleanup: true
21+
local_dir: ${FOLDER}
22+
github_token: ${TOKEN}

ReadMe.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
# WaterWheel
1+
# Iterable Observer
22

33
[Observable Proposal][1] implement based on [Async Generator (ES 2018)][2] & [TypeScript][3]
44

5+
[![Build Status](https://travis-ci.com/EasyWebApp/iterable-observer.svg?branch=master)][4]
6+
[![](https://data.jsdelivr.com/v1/package/npm/iterable-observer/badge?style=rounded)][5]
7+
8+
[![NPM](https://nodei.co/npm/iterable-observer.png?downloads=true&downloadRank=true&stars=true)][6]
9+
510
## Usage
611

12+
### Basic
13+
714
```javascript
8-
import { Observable } from 'waterwheel';
15+
import { Observable } from 'iterable-observer';
916

1017
var count = 0,
1118
list = [];
1219

13-
const observable = new Observable(({ next, complete }: Observer) => {
20+
const observable = new Observable(({ next, complete }) => {
1421
const timer = setInterval(
1522
() => (++count < 5 ? next(count) : complete(count)),
1623
0
@@ -26,6 +33,27 @@ const observable = new Observable(({ next, complete }: Observer) => {
2633
})();
2734
```
2835

36+
### Enhance Run-time platforms
37+
38+
```javascript
39+
import { Observable } from 'iterable-observer';
40+
41+
const reader = new FileReader(),
42+
{
43+
files: [file]
44+
} = document.querySelector('input[type="file"]');
45+
46+
reader.readAsBlob(file);
47+
48+
(async () => {
49+
for await (const { loaded } of Observable.fromEvent(reader, 'progress'))
50+
console.log((loaded / file.size) * 100 + '%');
51+
})();
52+
```
53+
2954
[1]: https://github.com/tc39/proposal-observable
3055
[2]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction-objects
3156
[3]: https://www.typescriptlang.org/
57+
[4]: https://travis-ci.com/EasyWebApp/iterable-observer
58+
[5]: https://www.jsdelivr.com/package/npm/iterable-observer
59+
[6]: https://nodei.co/npm/iterable-observer/

source/Observable.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,42 @@ export interface Observer<T = any> {
66
complete(): void;
77
}
88

9+
export interface Subscription {
10+
unsubscribe(): void;
11+
readonly closed: boolean;
12+
}
13+
14+
export interface Subscribable<T = any> {
15+
[Symbol.observable](): Subscribable<T>;
16+
subscribe(
17+
onNext: Observer<T>['next'],
18+
onError?: Observer<T>['error'],
19+
onComplete?: Observer<T>['complete']
20+
): Subscription;
21+
}
22+
923
export type SubscriberFunction = (observer: Observer) => (() => void) | void;
1024

11-
export class Observable<T = any> {
25+
export type EventHandler = (data: any) => void;
26+
27+
export interface EventTrigger {
28+
addEventListener?(name: string, handler: EventHandler): void;
29+
removeEventListener?(name: string, handler: EventHandler): void;
30+
on?(name: string, handler: EventHandler): this;
31+
off?(name: string, handler: EventHandler): this;
32+
}
33+
34+
export class Observable<T = any> implements Subscribable {
1235
private subscriber: SubscriberFunction;
1336

1437
constructor(subscriber: SubscriberFunction) {
1538
this.subscriber = subscriber;
1639
}
1740

41+
[Symbol.observable]() {
42+
return this;
43+
}
44+
1845
async *[Symbol.asyncIterator]() {
1946
var queue: Defer<T>[] = [makeDefer<T>()],
2047
canceler: (() => void) | void,
@@ -58,6 +85,18 @@ export class Observable<T = any> {
5885
});
5986
}
6087

88+
async toPromise() {
89+
const stack = [];
90+
91+
for await (const item of this) {
92+
stack.push(item);
93+
94+
if (stack.length > 2) stack.shift();
95+
}
96+
97+
return stack[0];
98+
}
99+
61100
subscribe(
62101
onNext: Observer<T>['next'],
63102
onError?: Observer<T>['error'],
@@ -87,10 +126,30 @@ export class Observable<T = any> {
87126
};
88127
}
89128

90-
static from<T = any>(observable: Observable<T>) {
129+
static from<T = any>(observable: Subscribable<T>) {
91130
return new this<T>(
92131
({ next, error, complete }) =>
93132
observable.subscribe(next, error, complete).unsubscribe
94133
);
95134
}
135+
136+
static fromEvent<T = any>(target: EventTrigger, name: string) {
137+
return new this<T>(({ next, error }) => {
138+
if (typeof target.on === 'function')
139+
target.on(name, next).on('error', error);
140+
else {
141+
target.addEventListener(name, next);
142+
target.addEventListener('error', error);
143+
}
144+
145+
return () => {
146+
if (typeof target.off === 'function')
147+
target.off(name, next).off('error', error);
148+
else {
149+
target.removeEventListener(name, next);
150+
target.removeEventListener('error', error);
151+
}
152+
};
153+
});
154+
}
96155
}

test/index.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Observable, Observer } from '../source';
2+
import { EventEmitter } from 'events';
23

34
function createExample() {
45
var timer: any,
@@ -59,6 +60,12 @@ describe('Observable', () => {
5960
expect(list).toEqual(expect.arrayContaining([1, 2, 3]));
6061
});
6162

63+
it('should convert to a Promise', () => {
64+
const observable = Observable.of<number>(1, 2, 3);
65+
66+
expect(observable.toPromise()).resolves.toBe(3);
67+
});
68+
6269
it('should invoke handlers after subscribing', async () => {
6370
const { subscriber, cleaner } = createExample();
6471

@@ -91,4 +98,29 @@ describe('Observable', () => {
9198
expect(list).toEqual(expect.arrayContaining([1, 2, 3, 4, 5]));
9299
});
93100
});
101+
102+
describe('3th Party Platform interfaces', () => {
103+
it('should listen Event Emitters', async () => {
104+
const target = new EventEmitter();
105+
var count = 0;
106+
107+
const observable = Observable.fromEvent(target, 'test'),
108+
list = [];
109+
110+
const timer = setInterval(() => {
111+
if (++count < 4) target.emit('test', count);
112+
else {
113+
clearInterval(timer);
114+
target.emit('error', 'example');
115+
}
116+
}, 0);
117+
118+
try {
119+
for await (const item of observable) list.push(item);
120+
} catch (error) {
121+
expect(list).toEqual(expect.arrayContaining([1, 2, 3]));
122+
expect(error).toBe('example');
123+
}
124+
});
125+
});
94126
});

0 commit comments

Comments
 (0)