This repository has been archived on 2024-07-27. You can view files and clone it, but cannot push or open issues or pull requests.
keksAccountGUI/node_modulesOLD/bfj/src/eventify.js
2019-08-11 20:48:02 +02:00

309 lines
7 KiB
JavaScript

'use strict'
const check = require('check-types')
const EventEmitter = require('events').EventEmitter
const events = require('./events')
const promise = require('./promise')
const invalidTypes = {
undefined: true, // eslint-disable-line no-undefined
function: true,
symbol: true
}
module.exports = eventify
/**
* Public function `eventify`.
*
* Returns an event emitter and asynchronously traverses a data structure
* (depth-first), emitting events as it encounters items. Sanely handles
* promises, buffers, maps and other iterables. The event emitter is
* decorated with a `pause` method that can be called to pause processing.
*
* @param data: The data structure to traverse.
*
* @option promises: 'resolve' or 'ignore', default is 'resolve'.
*
* @option buffers: 'toString' or 'ignore', default is 'toString'.
*
* @option maps: 'object' or 'ignore', default is 'object'.
*
* @option iterables: 'array' or 'ignore', default is 'array'.
*
* @option circular: 'error' or 'ignore', default is 'error'.
*
* @option yieldRate: The number of data items to process per timeslice,
* default is 16384.
*
* @option Promise: The promise constructor to use, defaults to bluebird.
**/
function eventify (data, options = {}) {
const coercions = {}
const emitter = new EventEmitter()
const Promise = promise(options)
const references = new Map()
let count = 0
let disableCoercions = false
let ignoreCircularReferences
let ignoreItems
let pause
let yieldRate
emitter.pause = () => {
let resolve
pause = new Promise(res => resolve = res)
return () => {
pause = null
count = 0
resolve()
}
}
parseOptions()
setImmediate(begin)
return emitter
function parseOptions () {
parseCoercionOption('promises')
parseCoercionOption('buffers')
parseCoercionOption('maps')
parseCoercionOption('iterables')
if (Object.keys(coercions).length === 0) {
disableCoercions = true
}
if (options.circular === 'ignore') {
ignoreCircularReferences = true
}
check.assert.maybe.positive(options.yieldRate)
yieldRate = options.yieldRate || 16384
}
function parseCoercionOption (key) {
if (options[key] !== 'ignore') {
coercions[key] = true
}
}
function begin () {
return proceed(data)
.catch(error => emit(events.error, error))
.then(() => emit(events.end))
}
function proceed (datum) {
if (++count % yieldRate !== 0) {
return coerce(datum).then(after)
}
return new Promise((resolve, reject) => {
setImmediate(() => {
coerce(datum)
.then(after)
.then(resolve)
.catch(reject)
})
})
function after (coerced) {
if (isInvalid(coerced)) {
return
}
if (coerced === false || coerced === true || coerced === null) {
return literal(coerced)
}
if (Array.isArray(coerced)) {
return array(coerced)
}
const type = typeof coerced
switch (type) {
case 'number':
return value(coerced, type)
case 'string':
return value(escapeString(coerced), type)
default:
return object(coerced)
}
}
}
function coerce (datum) {
if (disableCoercions || check.primitive(datum)) {
return Promise.resolve(datum)
}
if (check.instanceStrict(datum, Promise)) {
return coerceThing(datum, 'promises', coercePromise).then(coerce)
}
if (check.instanceStrict(datum, Buffer)) {
return coerceThing(datum, 'buffers', coerceBuffer)
}
if (check.instanceStrict(datum, Map)) {
return coerceThing(datum, 'maps', coerceMap)
}
if (
check.iterable(datum) &&
check.not.string(datum) &&
check.not.array(datum)
) {
return coerceThing(datum, 'iterables', coerceIterable)
}
if (check.function(datum.toJSON)) {
return Promise.resolve(datum.toJSON())
}
return Promise.resolve(datum)
}
function coerceThing (datum, thing, fn) {
if (coercions[thing]) {
return fn(datum)
}
return Promise.resolve()
}
function coercePromise (p) {
return p
}
function coerceBuffer (buffer) {
return Promise.resolve(buffer.toString())
}
function coerceMap (map) {
const result = {}
return coerceCollection(map, result, (item, key) => {
result[key] = item
})
}
function coerceCollection (coll, target, push) {
coll.forEach(push)
return Promise.resolve(target)
}
function coerceIterable (iterable) {
const result = []
return coerceCollection(iterable, result, item => {
result.push(item)
})
}
function isInvalid (datum) {
const type = typeof datum
return !! invalidTypes[type] || (
type === 'number' && ! isValidNumber(datum)
)
}
function isValidNumber (datum) {
return datum > Number.NEGATIVE_INFINITY && datum < Number.POSITIVE_INFINITY
}
function literal (datum) {
return value(datum, 'literal')
}
function value (datum, type) {
return emit(events[type], datum)
}
function emit (event, eventData) {
return (pause || Promise.resolve())
.then(() => emitter.emit(event, eventData))
.catch(err => {
try {
emitter.emit(events.error, err)
} catch (_) {
// When calling user code, anything is possible
}
})
}
function array (datum) {
// For an array, collection:object and collection:array are the same.
return collection(datum, datum, 'array', item => {
if (isInvalid(item)) {
return proceed(null)
}
return proceed(item)
})
}
function collection (obj, arr, type, action) {
let ignoreThisItem
return Promise.resolve()
.then(() => {
if (references.has(obj)) {
ignoreThisItem = ignoreItems = true
if (! ignoreCircularReferences) {
return emit(events.dataError, new Error('Circular reference.'))
}
} else {
references.set(obj, true)
}
})
.then(() => emit(events[type]))
.then(() => item(0))
function item (index) {
if (index >= arr.length) {
if (ignoreThisItem) {
ignoreItems = false
}
if (ignoreItems) {
return Promise.resolve()
}
return emit(events.endPrefix + events[type])
.then(() => references.delete(obj))
}
if (ignoreItems) {
return item(index + 1)
}
return action(arr[index])
.then(() => item(index + 1))
}
}
function object (datum) {
// For an object, collection:object and collection:array are different.
return collection(datum, Object.keys(datum), 'object', key => {
const item = datum[key]
if (isInvalid(item)) {
return Promise.resolve()
}
return emit(events.property, escapeString(key))
.then(() => proceed(item))
})
}
function escapeString (string) {
string = JSON.stringify(string)
return string.substring(1, string.length - 1)
}
}