Last active
March 28, 2023 18:39
-
-
Save zerkalica/4a5260bf6fcd57492946c463b8cfc0f6 to your computer and use it in GitHub Desktop.
batch fetch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type DataError = { | |
message?: string; | |
code: string; | |
} | |
class BatchError extends Error { | |
constructor(message: string, readonly code: string) { | |
super(message) | |
} | |
} | |
class Batcher<Data> { | |
// На сервер отправляется мапа id: значение | |
// Если значение null - сущность удалится, если Partial<Data> - пропатчится | |
// значение undefined - означает загрузить сущность по id в ключе, | |
// однако id: undefined вырежутся при сериализации в json | |
// поэтому отправляем id в массиве в _ids | |
// Сервер может вернуть ошибку на каждый id в соответствии с переданной мапой | |
// Или полную сущность (при загрузке или патче) или null при удалении | |
fetch(patch: Record<string, null | undefined | Partial<Data>> & { _ids?: string[] }) { | |
// Сервер возвращает id-мапу сущностей и id-мапу ошибок, если есть | |
return {} as { data?: Record<string, Data | null>; errors?: Record<string, DataError> } | |
} | |
// values кэшится по ключу timer_id | |
// Для разных таймеров будут разные timer_id и непересекающиеся наборы id в параметрах фетча (в params_get) | |
@ $mol_mem_key | |
values(timer_id: number) { | |
const patch = this.params_get() | |
const ids = Object.keys(patch) | |
const result = this.fetch( { ...patch, _ids: ids.filter(id => patch[id] === undefined) } ) | |
const next = {} as Record<string, Data | Error | null> | |
for (const id of ids) { | |
const data = result?.data?.[id] | |
let error = result?.errors?.[id] | |
// Сервер не вернул ошибку и забыл отдать данные по какому-то из id, интерпретируем это как ошибку | |
if (data === undefined && ! error) error = { message: `Unknown error fetching ${id}`, code: 'notfound' } | |
// Конвертим объект с ошибкой в полноценный Error | |
// если value вернет ошибку, она автоматически бросится | |
const err = error ? new BatchError(error.message ?? error.code, error.code) : undefined | |
next[id] = err ?? data | |
// Если от конкретного value(id) отписались, то удаляем его из мапы, очищая память | |
// destructor поддерживается в mol_wire, | |
// что бы оно не всплывало в Object.keys, делаем его не перечисляемым | |
if (next[id] && typeof next[id] === 'object') Object.defineProperty(next, 'desctructor', { | |
value() { delete next[id] }, | |
enumerable: false | |
}) | |
} | |
return next | |
} | |
protected timer_id = 0 | |
protected patch = {} as Record<string, null | undefined | Partial<Data>> | |
// В экшене, что бы перезапуски this.values не меняли timer_id и не очищали this.patch | |
// sleep бросает Promise с setTimeout | |
// Пока тред спит, все обращения к this.value складывают параметры запроса в this.patch | |
// По истечении таймера, timer_id увеличивается, this.patch очищается | |
// а его текущее значение запоминается в this.values, пока он не зафетчит данные | |
@ $mol_action | |
params_get() { | |
sleep(100) | |
this.timer_id++ | |
// с этого момента все обращения к this.value будут батчится в другой timer_id | |
const patch = this.patch | |
this.patch = {} | |
return patch | |
} | |
// В экшене, что б при перезапусках не менялся timer_id относительно value | |
// и не мутировался постоянно this.patch | |
// Т.к. он может быть использован уже в другом таймере | |
@ $mol_action | |
params_set(id: string, next?: Partial<Data> | null) { | |
this.patch[id] = next | |
return this.timer_id | |
} | |
// value(id) - получить данные по id | |
// value(id, null) - удалить, возвращает null | |
// value(id, { title: 'some' }) - патчить часть объекта на сервере, возвращает полную сущность | |
@ $mol_mem_key | |
value(id: string, next?: Partial<Data> | null) { | |
const timer_id = this.params_set(id, next) | |
return this.values(timer_id)[id] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment