Skip to content

Commit 4763e77

Browse files
committed
v6.x.x
- Implement event driven pattern - Simplify core logic
1 parent 8169502 commit 4763e77

File tree

11 files changed

+257
-168
lines changed

11 files changed

+257
-168
lines changed

README.md

Lines changed: 79 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,14 @@ read(url).then((feed) => {
3030
})
3131
```
3232

33-
##### Note:
34-
35-
> Since Node.js v14, ECMAScript modules [have became the official standard format](https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_modules_ecmascript_modules).
36-
> Just ensure that you are [using module system](https://nodejs.org/api/packages.html#determining-module-system) and enjoy with ES6 import/export syntax.
37-
38-
3933
## APIs
4034

4135
- [.read(String url)](#readstring-url)
36+
- [The events](#the-events)
37+
- [.resetEvents()](#)
4238
- [Configuration methods](#configuration-methods)
4339

44-
#### read(String url)
40+
### read(String url)
4541

4642
Load and extract feed data from given RSS/ATOM source. Return a Promise object.
4743

@@ -55,9 +51,10 @@ import {
5551
const getFeedData = async (url) => {
5652
try {
5753
console.log(`Get feed data from ${url}`)
58-
const data = await read(url)
59-
console.log(data)
60-
return data
54+
const result = await read(url)
55+
// result may be feed data or null
56+
console.log(result)
57+
return result
6158
} catch (err) {
6259
console.trace(err)
6360
}
@@ -89,27 +86,84 @@ Feed data object retuned by `read()` method should look like below:
8986
}
9087
```
9188

92-
#### Configuration methods
89+
### The events
9390

94-
In addition, this lib provides some methods to customize default settings. Don't touch them unless you have reason to do that.
91+
Since v6.0.0, `feed-reader` supports event-driven pattern for easier writing code with more control.
9592

96-
- getRequestOptions()
97-
- setRequestOptions(Object requestOptions)
93+
- `onSuccess(Function callback)`
94+
- `onError(Function callback)`
95+
- `onComplete(Function callback)`
9896

99-
#### Object `requestOptions`:
97+
The following example will explain better than any word:
10098

10199
```js
102-
{
103-
headers: {
104-
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0'
105-
},
106-
responseType: 'text',
107-
responseEncoding: 'utf8',
108-
timeout: 6e4, // 1 minute
109-
maxRedirects: 3
110-
}
100+
import { read, onSuccess, onError, onComplete } from 'feed-reader'
101+
102+
onSuccess((feed, url) => {
103+
console.log(`Feed data from ${url} has been parsed successfully`)
104+
console.log('`feed` is always an object that contains feed data')
105+
console.log(feed)
106+
})
107+
108+
onError((err, url) => {
109+
console.log(`Error occurred while processing ${url}`)
110+
console.log('There is a message and reason:')
111+
console.log(err)
112+
})
113+
114+
onComplete((result, url) => {
115+
console.log(`Finish processing ${url}`)
116+
console.log('There may or may not be an error')
117+
console.log('`result` may be feed data or null')
118+
console.log(result)
119+
})
120+
121+
read('https://news.google.com/rss')
122+
read('https://google.com')
111123
```
112-
Read [axios' request config](https://axios-http.com/docs/req_config) for more info.
124+
125+
We can mix both style together, for example to handle the error:
126+
127+
```js
128+
import { read, onError } from 'feed-reader'
129+
130+
onError((err, url) => {
131+
console.log(`Error occurred while processing ${url}`)
132+
console.log('There is a message and reason:')
133+
console.log(err)
134+
})
135+
136+
const getFeedData = async (url) => {
137+
const result = await read(url)
138+
// `result` may be feed data or null
139+
return result
140+
}
141+
142+
getFeedData('https://news.google.com/rss')
143+
````
144+
145+
#### Reset event listeners
146+
147+
Use method `resetEvents()` when you want to clear registered listeners from all events.
148+
149+
```js
150+
import { resetEvents } from 'feed-reader'
151+
152+
resetEvents()
153+
````
154+
155+
### Configuration methods
156+
157+
#### `setRequestOptions(Object requestOptions)`
158+
159+
Affect to the way how `axios` works. Please refer [axios' request config](https://axios-http.com/docs/req_config) for more info.
160+
161+
#### `getRequestOptions()`
162+
163+
Return current request options.
164+
165+
Default values can be found [here](https://github.com/ndaidong/feed-reader/blob/main/src/config.js#L5).
166+
113167
114168
## Test
115169

cjs-eval.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,28 @@
22

33
const { writeFileSync } = require('fs')
44

5-
const { read } = require('./dist/cjs/feed-reader.js')
5+
const { read, onSuccess, onError, onComplete } = require('./dist/cjs/feed-reader.js')
66

77
const extractFromUrl = async (url) => {
8-
try {
9-
const art = await read(url)
10-
console.log(art)
11-
writeFileSync('./output.json', JSON.stringify(art), 'utf8')
12-
} catch (err) {
13-
console.trace(err)
14-
}
8+
onComplete((result, url) => {
9+
console.log('onComplete', url)
10+
})
11+
onSuccess((feed, url) => {
12+
console.log('onSuccess', url)
13+
writeFileSync('./output.json', JSON.stringify(feed, undefined, 2), 'utf8')
14+
})
15+
onError((e, url) => {
16+
console.log('onError', url)
17+
console.log(e)
18+
})
19+
const feed = await read(url)
20+
console.log(feed)
1521
}
1622

1723
const init = (argv) => {
1824
if (argv.length === 3) {
19-
return extractFromUrl(argv[2])
25+
const isUrl = argv[2]
26+
return isUrl ? extractFromUrl(isUrl) : false
2027
}
2128
return 'Nothing to do!'
2229
}

eval.js

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,29 @@
11
// eval.js
22

3-
import { readFileSync, writeFileSync, existsSync } from 'fs'
3+
import { writeFileSync } from 'fs'
44

5-
import isValidUrl from './src/utils/isValidUrl.js'
6-
import { read } from './src/main.js'
5+
import { read, onComplete, onSuccess, onError } from './src/main.js'
76

87
const extractFromUrl = async (url) => {
9-
try {
10-
const art = await read(url)
11-
console.log(art)
12-
writeFileSync('./output.json', JSON.stringify(art), 'utf8')
13-
} catch (err) {
14-
console.trace(err)
15-
}
16-
}
17-
18-
const extractFromFile = async (fpath) => {
19-
try {
20-
const xml = readFileSync(fpath, 'utf8')
21-
const art = await read(xml)
22-
console.log(art)
23-
writeFileSync('./output.json', JSON.stringify(art), 'utf8')
24-
} catch (err) {
25-
console.trace(err)
26-
}
8+
onComplete((result, url) => {
9+
console.log('onComplete', url)
10+
})
11+
onSuccess((feed, url) => {
12+
console.log('onSuccess', url)
13+
writeFileSync('./output.json', JSON.stringify(feed, undefined, 2), 'utf8')
14+
})
15+
onError((e, url) => {
16+
console.log('onError', url)
17+
console.log(e)
18+
})
19+
const feed = await read(url)
20+
console.log(feed)
2721
}
2822

2923
const init = (argv) => {
3024
if (argv.length === 3) {
31-
const input = argv[2]
32-
const isUrl = isValidUrl(input)
33-
return isUrl ? extractFromUrl(input) : existsSync(input) ? extractFromFile(input) : false
25+
const isUrl = argv[2]
26+
return isUrl ? extractFromUrl(isUrl) : false
3427
}
3528
return 'Nothing to do!'
3629
}

src/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const requestOptions = {
99
},
1010
responseType: 'text',
1111
responseEncoding: 'utf8',
12-
timeout: 6e4, // 1 minute
12+
timeout: 3e4, // 30 seconds
1313
maxRedirects: 5
1414
}
1515

src/main.js

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,84 @@
33
* @ndaidong
44
**/
55

6-
import { info } from './utils/logger.js'
6+
import EventEmitter from 'events'
77

88
import getXML from './utils/retrieve.js'
9-
import xml2obj from './utils/xml2obj.js'
10-
import { parseRSS, parseAtom } from './utils/parser.js'
9+
import { parse } from './utils/parser.js'
1110

12-
import {
13-
validate,
14-
isRSS,
15-
isAtom
16-
} from './utils/validator.js'
11+
import isValidUrl from './utils/isValidUrl.js'
12+
import { validate } from './utils/validator.js'
1713

1814
export {
1915
getRequestOptions,
2016
setRequestOptions
2117
} from './config.js'
2218

19+
const eventEmitter = new EventEmitter()
20+
21+
const runWhenComplete = (result, url) => {
22+
eventEmitter.emit('complete', result, url)
23+
return result
24+
}
25+
2326
export const read = async (url) => {
24-
const xmldata = await getXML(url)
27+
try {
28+
if (!isValidUrl(url)) {
29+
eventEmitter.emit('error', {
30+
error: 'Error occurred while verifying feed URL',
31+
reason: 'Invalid URL'
32+
}, url)
2533

26-
const { xml, error } = xmldata
27-
if (error) {
28-
throw error
29-
}
34+
return runWhenComplete(null, url)
35+
}
36+
const xml = await getXML(url)
37+
38+
if (!validate(xml)) {
39+
eventEmitter.emit('error', {
40+
error: 'Error occurred while validating XML format',
41+
reason: 'The XML document is not well-formed'
42+
}, url)
43+
44+
return runWhenComplete(null, url)
45+
}
46+
47+
try {
48+
const feed = parse(xml)
49+
if (feed) {
50+
eventEmitter.emit('success', feed, url)
51+
}
52+
53+
return runWhenComplete(feed, url)
54+
} catch (er) {
55+
eventEmitter.emit('error', {
56+
error: 'Error occurred while parsing XML structure',
57+
reason: er.message
58+
}, url)
3059

31-
if (!validate(xml)) {
32-
throw new Error(`Failed while validating XML format from "${url}"`)
60+
return runWhenComplete(null, url)
61+
}
62+
} catch (err) {
63+
eventEmitter.emit('error', {
64+
error: 'Error occurred while retrieving remote XML data',
65+
reason: err.message
66+
}, url)
67+
68+
return runWhenComplete(null, url)
3369
}
70+
}
71+
72+
export const onComplete = (fn) => {
73+
eventEmitter.on('complete', fn)
74+
}
75+
76+
export const onSuccess = (fn) => {
77+
eventEmitter.on('success', fn)
78+
}
79+
80+
export const onError = (fn) => {
81+
eventEmitter.on('error', fn)
82+
}
3483

35-
info('Parsing XML data...')
36-
const jsonObj = xml2obj(xml)
37-
return isRSS(jsonObj) ? parseRSS(jsonObj) : isAtom(jsonObj) ? parseAtom(jsonObj) : null
84+
export const resetEvents = () => {
85+
eventEmitter.removeAllListeners()
3886
}

0 commit comments

Comments
 (0)