Implement asyncSearch (WIP)

This commit is contained in:
Jos de Jong 2020-09-16 21:19:51 +02:00
parent ac078e445b
commit fd673a4ea0
3 changed files with 180 additions and 2 deletions

View File

@ -284,8 +284,6 @@ export function createNewValue (doc, selection, type) {
: ''
})
console.log('structure', jsonExample, structure)
return structure
} else {
// no example structure

View File

@ -130,6 +130,85 @@ function searchRecursive (key, doc, searchText) {
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) {
const resultArray = []

101
src/logic/search.test.js Normal file
View File

@ -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()
})
})
})