source: frontend/node_modules/bfj/src/eventify.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.0 KB
Line 
1'use strict'
2
3const check = require('check-types')
4const EventEmitter = require('events').EventEmitter
5const events = require('./events')
6const promise = require('./promise')
7
8const invalidTypes = {
9 undefined: true, // eslint-disable-line no-undefined
10 function: true,
11 symbol: true
12}
13
14module.exports = eventify
15
16/**
17 * Public function `eventify`.
18 *
19 * Returns an event emitter and asynchronously traverses a data structure
20 * (depth-first), emitting events as it encounters items. Sanely handles
21 * promises, buffers, maps and other iterables. The event emitter is
22 * decorated with a `pause` method that can be called to pause processing.
23 *
24 * @param data: The data structure to traverse.
25 *
26 * @option promises: 'resolve' or 'ignore', default is 'resolve'.
27 *
28 * @option buffers: 'toString' or 'ignore', default is 'toString'.
29 *
30 * @option maps: 'object' or 'ignore', default is 'object'.
31 *
32 * @option iterables: 'array' or 'ignore', default is 'array'.
33 *
34 * @option circular: 'error' or 'ignore', default is 'error'.
35 *
36 * @option yieldRate: The number of data items to process per timeslice,
37 * default is 16384.
38 *
39 * @option Promise: The promise constructor to use, defaults to bluebird.
40 **/
41function eventify (data, options = {}) {
42 const coercions = {}
43 const emitter = new EventEmitter()
44 const Promise = promise(options)
45 const references = new Map()
46
47 let count = 0
48 let disableCoercions = false
49 let ignoreCircularReferences
50 let ignoreItems
51 let pause
52 let yieldRate
53
54 emitter.pause = () => {
55 let resolve
56 pause = new Promise(res => resolve = res)
57 return () => {
58 pause = null
59 count = 0
60 resolve()
61 }
62 }
63 parseOptions()
64 setImmediate(begin)
65
66 return emitter
67
68 function parseOptions () {
69 parseCoercionOption('promises')
70 parseCoercionOption('buffers')
71 parseCoercionOption('maps')
72 parseCoercionOption('iterables')
73
74 if (Object.keys(coercions).length === 0) {
75 disableCoercions = true
76 }
77
78 if (options.circular === 'ignore') {
79 ignoreCircularReferences = true
80 }
81
82 check.assert.maybe.positive(options.yieldRate)
83 yieldRate = options.yieldRate || 16384
84 }
85
86 function parseCoercionOption (key) {
87 if (options[key] !== 'ignore') {
88 coercions[key] = true
89 }
90 }
91
92 function begin () {
93 return proceed(data)
94 .catch(error => emit(events.error, error))
95 .then(() => emit(events.end))
96 }
97
98 function proceed (datum) {
99 if (++count % yieldRate !== 0) {
100 return coerce(datum).then(after)
101 }
102
103 return new Promise((resolve, reject) => {
104 setImmediate(() => {
105 coerce(datum)
106 .then(after)
107 .then(resolve)
108 .catch(reject)
109 })
110 })
111
112 function after (coerced) {
113 if (isInvalid(coerced)) {
114 return
115 }
116
117 if (coerced === false || coerced === true || coerced === null) {
118 return literal(coerced)
119 }
120
121 if (Array.isArray(coerced)) {
122 return array(coerced)
123 }
124
125 const type = typeof coerced
126
127 switch (type) {
128 case 'number':
129 return value(coerced, type)
130 case 'string':
131 return value(escapeString(coerced), type)
132 default:
133 return object(coerced)
134 }
135 }
136 }
137
138 function coerce (datum) {
139 if (disableCoercions || check.primitive(datum)) {
140 return Promise.resolve(datum)
141 }
142
143 if (check.thenable(datum)) {
144 return coerceThing(datum, 'promises', coercePromise).then(coerce)
145 }
146
147 if (check.instanceStrict(datum, Buffer)) {
148 return coerceThing(datum, 'buffers', coerceBuffer)
149 }
150
151 if (check.instanceStrict(datum, Map)) {
152 return coerceThing(datum, 'maps', coerceMap)
153 }
154
155 if (
156 check.iterable(datum) &&
157 check.not.string(datum) &&
158 check.not.array(datum)
159 ) {
160 return coerceThing(datum, 'iterables', coerceIterable)
161 }
162
163 if (check.function(datum.toJSON)) {
164 return Promise.resolve(datum.toJSON())
165 }
166
167 return Promise.resolve(datum)
168 }
169
170 function coerceThing (datum, thing, fn) {
171 if (coercions[thing]) {
172 return fn(datum)
173 }
174
175 return Promise.resolve()
176 }
177
178 function coercePromise (p) {
179 return p
180 }
181
182 function coerceBuffer (buffer) {
183 return Promise.resolve(buffer.toString())
184 }
185
186 function coerceMap (map) {
187 const result = {}
188
189 return coerceCollection(map, result, (item, key) => {
190 result[key] = item
191 })
192 }
193
194 function coerceCollection (coll, target, push) {
195 coll.forEach(push)
196
197 return Promise.resolve(target)
198 }
199
200 function coerceIterable (iterable) {
201 const result = []
202
203 return coerceCollection(iterable, result, item => {
204 result.push(item)
205 })
206 }
207
208 function isInvalid (datum) {
209 const type = typeof datum
210 return !! invalidTypes[type] || (
211 type === 'number' && ! isValidNumber(datum)
212 )
213 }
214
215 function isValidNumber (datum) {
216 return datum > Number.NEGATIVE_INFINITY && datum < Number.POSITIVE_INFINITY
217 }
218
219 function literal (datum) {
220 return value(datum, 'literal')
221 }
222
223 function value (datum, type) {
224 return emit(events[type], datum)
225 }
226
227 function emit (event, eventData) {
228 return (pause || Promise.resolve())
229 .then(() => emitter.emit(event, eventData))
230 .catch(err => {
231 try {
232 emitter.emit(events.error, err)
233 } catch (_) {
234 // When calling user code, anything is possible
235 }
236 })
237 }
238
239 function array (datum) {
240 // For an array, collection:object and collection:array are the same.
241 return collection(datum, datum, 'array', item => {
242 if (isInvalid(item)) {
243 return proceed(null)
244 }
245
246 return proceed(item)
247 })
248 }
249
250 function collection (obj, arr, type, action) {
251 let ignoreThisItem
252
253 return Promise.resolve()
254 .then(() => {
255 if (references.has(obj)) {
256 ignoreThisItem = ignoreItems = true
257
258 if (! ignoreCircularReferences) {
259 return emit(events.dataError, new Error('Circular reference.'))
260 }
261 } else {
262 references.set(obj, true)
263 }
264 })
265 .then(() => emit(events[type]))
266 .then(() => item(0))
267
268 function item (index) {
269 if (index >= arr.length) {
270 if (ignoreThisItem) {
271 ignoreItems = false
272 }
273
274 if (ignoreItems) {
275 return Promise.resolve()
276 }
277
278 return emit(events.endPrefix + events[type])
279 .then(() => references.delete(obj))
280 }
281
282 if (ignoreItems) {
283 return item(index + 1)
284 }
285
286 return action(arr[index])
287 .then(() => item(index + 1))
288 }
289 }
290
291 function object (datum) {
292 // For an object, collection:object and collection:array are different.
293 return collection(datum, Object.keys(datum), 'object', key => {
294 const item = datum[key]
295
296 if (isInvalid(item)) {
297 return Promise.resolve()
298 }
299
300 return emit(events.property, escapeString(key))
301 .then(() => proceed(item))
302 })
303 }
304
305 function escapeString (string) {
306 string = JSON.stringify(string)
307 return string.substring(1, string.length - 1)
308 }
309}
Note: See TracBrowser for help on using the repository browser.