Implement asyncSearch (WIP)
This commit is contained in:
parent
ac078e445b
commit
fd673a4ea0
|
@ -284,8 +284,6 @@ export function createNewValue (doc, selection, type) {
|
||||||
: ''
|
: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('structure', jsonExample, structure)
|
|
||||||
|
|
||||||
return structure
|
return structure
|
||||||
} else {
|
} else {
|
||||||
// no example structure
|
// no example structure
|
||||||
|
|
|
@ -130,6 +130,85 @@ function searchRecursive (key, doc, searchText) {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function tick () {
|
||||||
|
return new Promise(setTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: comment
|
||||||
|
export function searchAsync (searchText, doc, { onPartlyResults, onDone }) {
|
||||||
|
const yieldAfterItemCount = 10000 // TODO: what is a good value?
|
||||||
|
const search = searchGenerator(searchText, doc, yieldAfterItemCount)
|
||||||
|
|
||||||
|
// TODO: implement pause after having found x results (like 999)
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
const results = []
|
||||||
|
|
||||||
|
async function executeSearch () {
|
||||||
|
let next
|
||||||
|
do {
|
||||||
|
next = search.next()
|
||||||
|
if (next.value) {
|
||||||
|
results.push(next.value) // TODO: make this immutable?
|
||||||
|
onPartlyResults(results)
|
||||||
|
}
|
||||||
|
await tick() // TODO: be able to wait longer than just one tick? So the UI stays fully responsive?
|
||||||
|
} while (!cancelled && !next.done)
|
||||||
|
|
||||||
|
if (next.done) {
|
||||||
|
onDone(results)
|
||||||
|
} // else: cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
// start searching on the next tick
|
||||||
|
setTimeout(executeSearch)
|
||||||
|
|
||||||
|
return {
|
||||||
|
cancel: () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: comment
|
||||||
|
export function * searchGenerator (searchText, doc, yieldAfterItemCount = undefined) {
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
function * incrementCounter () {
|
||||||
|
count++
|
||||||
|
if (typeof yieldAfterItemCount === 'number' && count % yieldAfterItemCount === 0) {
|
||||||
|
// pause every x items
|
||||||
|
yield null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function * searchRecursiveAsync (searchText, doc, path) {
|
||||||
|
const type = valueType(doc)
|
||||||
|
|
||||||
|
if (type === 'array') {
|
||||||
|
for (let i = 0; i < doc.length; i++) {
|
||||||
|
yield * searchRecursiveAsync(searchText, doc[i], path.concat([i]))
|
||||||
|
}
|
||||||
|
} else if (type === 'object') {
|
||||||
|
for (const prop of Object.keys(doc)) {
|
||||||
|
if (typeof prop === 'string' && containsCaseInsensitive(prop, searchText)) {
|
||||||
|
yield path.concat([prop, STATE_SEARCH_PROPERTY])
|
||||||
|
}
|
||||||
|
yield * incrementCounter()
|
||||||
|
|
||||||
|
yield * searchRecursiveAsync(searchText, doc[prop], path.concat([prop]))
|
||||||
|
}
|
||||||
|
} else { // type is a value
|
||||||
|
if (containsCaseInsensitive(doc, searchText)) {
|
||||||
|
yield path.concat([STATE_SEARCH_VALUE])
|
||||||
|
}
|
||||||
|
yield * incrementCounter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return yield * searchRecursiveAsync(searchText, doc, [])
|
||||||
|
}
|
||||||
|
|
||||||
function flattenSearch (searchResult) {
|
function flattenSearch (searchResult) {
|
||||||
const resultArray = []
|
const resultArray = []
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import { times } from 'lodash-es'
|
||||||
|
import { searchAsync, searchGenerator } from './search.js'
|
||||||
|
import { STATE_SEARCH_PROPERTY, STATE_SEARCH_VALUE } from '../constants.js'
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
it('should search with generator', () => {
|
||||||
|
const doc = {
|
||||||
|
b: { c: 'a' },
|
||||||
|
a: [
|
||||||
|
{ a: 'b', c: 'a' },
|
||||||
|
'e',
|
||||||
|
'a'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = searchGenerator('a', doc)
|
||||||
|
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: ['b', 'c', STATE_SEARCH_VALUE] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: ['a', STATE_SEARCH_PROPERTY] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: ['a', 0, 'a', STATE_SEARCH_PROPERTY] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: ['a', 0, 'c', STATE_SEARCH_VALUE] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: ['a', 2, STATE_SEARCH_VALUE] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: true, value: undefined })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should yield every x items during search', () => {
|
||||||
|
const doc = times(30, index => String(index))
|
||||||
|
|
||||||
|
const search = searchGenerator('4', doc, 10)
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: [4, STATE_SEARCH_VALUE] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: null }) // at 10
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: [14, STATE_SEARCH_VALUE] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: null }) // at 20
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: [24, STATE_SEARCH_VALUE] })
|
||||||
|
assert.deepStrictEqual(search.next(), { done: false, value: null }) // at 30
|
||||||
|
assert.deepStrictEqual(search.next(), { done: true, value: undefined })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should search async', (done) => {
|
||||||
|
const doc = times(30, index => String(index))
|
||||||
|
|
||||||
|
const callbacks = []
|
||||||
|
|
||||||
|
function onPartlyResults (results) {
|
||||||
|
callbacks.push(results.slice(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDone (results) {
|
||||||
|
assert.deepStrictEqual(results, [
|
||||||
|
[4, STATE_SEARCH_VALUE],
|
||||||
|
[14, STATE_SEARCH_VALUE],
|
||||||
|
[24, STATE_SEARCH_VALUE]
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.deepStrictEqual(callbacks, [
|
||||||
|
results.slice(0, 1),
|
||||||
|
results.slice(0, 2),
|
||||||
|
results.slice(0, 3)
|
||||||
|
])
|
||||||
|
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAsync('4', doc, { onPartlyResults, onDone })
|
||||||
|
|
||||||
|
// should not have results right after creation, but only on the first next tick
|
||||||
|
assert.deepStrictEqual(callbacks, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should cancel async search', (done) => {
|
||||||
|
const doc = times(30, index => String(index))
|
||||||
|
|
||||||
|
const callbacks = []
|
||||||
|
|
||||||
|
function onPartlyResults (results) {
|
||||||
|
callbacks.push(results.slice(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDone () {
|
||||||
|
throw new Error('onDone should not be invoked')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cancel } = searchAsync('4', doc, { onPartlyResults, onDone })
|
||||||
|
|
||||||
|
// should not have results right after creation, but only on the first next tick
|
||||||
|
assert.deepStrictEqual(callbacks, [])
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
assert.deepStrictEqual(callbacks, [
|
||||||
|
[
|
||||||
|
[4, STATE_SEARCH_VALUE]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue