В библиотеке Dexie.js операции выборки данных часто строятся вокруг
необходимости быстро определить наличие хотя бы одной записи,
удовлетворяющей условиям запроса. В таких сценариях вместо полного
извлечения коллекции применяется ленивое чтение первого или последнего
элемента через методы first() и last(). Эти
методы позволяют минимизировать объём извлекаемых данных и сократить
количество операций чтения из IndexedDB.
Метод first() возвращает первую запись из
результата запроса. Порядок определяется индексом или
естественным порядком сортировки ключей.
Базовое использование:
const user = await db.users
.where('age')
.above(18)
.first();
В этом примере:
age> 18Если записей нет, результатом будет undefined.
first() не загружает всю выборку. Внутри Dexie.js
формируется ограниченный запрос с лимитом 1, что делает
операцию эффективной даже при больших таблицах.
Часто first() применяется не для получения данных, а для
проверки факта наличия записи:
const exists = await db.users
.where('email')
.equals('test@example.com')
.first()
.then(Boolean);
Здесь:
Boolean даёт true или
falseАльтернативная форма:
const exists = !!(await db.users
.where('email')
.equals('test@example.com')
.first());
Метод last() возвращает последнюю запись из
результата запроса, учитывая порядок сортировки.
Пример:
const latestOrder = await db.orders
.orderBy('createdAt')
.last();
Здесь:
createdAtЕсли сортировка не задана явно, используется порядок индекса или первичного ключа.
Метод last() может быть логически заменён
конструкцией:
const latest = await db.orders
.orderBy('createdAt')
.reverse()
.first();
Обе операции эквивалентны, но есть различия:
last() выполняет получение последнего элемента
напрямуюreverse().first() разворачивает курсор и берёт первый
элементВнутренняя реализация Dexie.js оптимизирует оба варианта до индексного чтения с ограничением одного элемента.
Хотя менее распространено, last() также может
использоваться для проверки наличия записей:
const exists = await db.sessions
.where('userId')
.equals(42)
.last()
.then(Boolean);
Такой подход полезен, если важен именно последний элемент выборки, например при работе с логами или событиями.
Ключевая разница заключается в объёме данных:
await db.users.where('active').equals(1).first(); // один объект
await db.users.where('active').equals(1).toArray(); // массив всех объектов
first() — минимальная выборка, один элементtoArray() — полная загрузка результатаПри проверке существования использование toArray()
является неэффективным, так как приводит к избыточной передаче
данных.
Эффективность first() и last() напрямую
зависит от наличия индексов:
db.version(1).stores({
users: '++id,email,age'
});
При наличии индекса:
Без индекса:
first() фактически становится полным scan-оператором с
остановкой на первом совпаденииПри использовании составных условий:
const record = await db.logs
.where('[userId+type]')
.equals([42, 'error'])
.first();
оператор first() возвращает первую запись в порядке
составного индекса. Это особенно важно, когда ключ формируется из
нескольких полей.
Метод orderBy() влияет на результат first()
и last():
const min = await db.products
.orderBy('price')
.first();
const max = await db.products
.orderBy('price')
.last();
first() — минимальное значениеlast() — максимальное значениеТаким образом, оба метода часто используются для вычисления границ диапазона данных без агрегаций.
При конкурентных изменениях данных:
const session = await db.sessions
.where('active')
.equals(1)
.first();
возможна ситуация, когда запись уже удалена между моментом запроса и
обработкой результата. Поэтому результат first() всегда
должен рассматриваться как потенциально устаревший снимок состояния
базы.
first() и last() всегда возвращают один
объект или undefinedorderBy()| Метод | Результат | Объём данных | Типичная задача |
|---|---|---|---|
| first() | первый элемент | минимальный | проверка существования, выбор первого совпадения |
| last() | последний элемент | минимальный | поиск последней записи, максимум диапазона |
| toArray() | массив | полный | загрузка всех совпадений |
const exists = await db.users
.where('username')
.equals(name)
.first();
const event = await db.events
.orderBy('timestamp')
.last();
const cheapest = await db.products
.orderBy('price')
.first();
Оба метода возвращают Promise:
db.users
.where('role')
.equals('admin')
.first()
.then(user => {
if (user) {
// обработка
}
});
или в async/await стиле:
const user = await db.users
.where('role')
.equals('admin')
.first();
Отсутствие результата выражается через undefined:
const item = await db.items.where('id').equals(999).first();
if (!item) {
// запись не найдена
}
Это поведение делает first() удобным инструментом для
булевой логики без дополнительных проверок длины массива.
const firstInRange = await db.sales
.where('amount')
.between(100, 500)
.first();
const lastInRange = await db.sales
.where('amount')
.between(100, 500)
.last();
Здесь:
first() возвращает минимальное значение в
диапазонеlast() возвращает максимальное значение в
диапазонеОба метода позволяют реализовать границы диапазона без дополнительной обработки коллекций.