Постраничная выдача
Конверты со смещением и с курсором и почему общее количество иногда отсутствует, а не равно нулю.
Любой эндпоинт-коллекция возвращает конверт, а не голый JSON-массив:
{
"data": [ ... ],
"pagination": { ... }
}
Массив всегда лежит под data, а состояние выдачи — всегда под pagination, и
никогда плоско в корне ответа. Именно это позволяет одному клиентскому хелперу
обходить любую коллекцию API, не зная, с каким эндпоинтом он говорит.
Выдача со смещением — вариант по умолчанию
Большинство коллекций листается смещением. Передавайте limit и offset в
query-параметрах:
curl 'https://api.foxguide.io/v1/tables?limit=20&offset=40' \
-H 'Authorization: Bearer fgd_live_...'
{
"data": [ ... 20 элементов ... ],
"pagination": { "total": 137, "limit": 20, "offset": 40, "hasMore": true }
}
Значения по умолчанию — limit: 20, offset: 0. Эндпоинт может ограничивать
limit сверху; запрос выше потолка обрезается, а не отклоняется. Останавливайтесь
по hasMore: false — это единственное поле, присутствующее в каждом
постраничном ответе, и именно его следует брать условием цикла.
Выдача с курсором — коллекции высокой интенсивности
Эндпоинты над часто меняющимися данными (списки сообщений, журналы событий и доставок) листаются курсором. Листание смещением по коллекции, в которую параллельно пишут, пропускает и повторяет строки; курсор — нет.
{
"data": [ ... ],
"pagination": {
"hasMore": true,
"cursor": "6712f0b3c4a1e2d3f4a5b6c7",
"direction": "next",
"limit": 50
}
}
Передайте полученный cursor обратно в query-параметре cursor, чтобы получить
следующую страницу. Два свойства cursor стоит учесть в клиенте:
- Он может быть явным
null, а не просто отсутствовать, когда следующей страницы нет. Обрабатывайтеnullи отсутствие одинаково, а условием цикла в любом случае беритеhasMore. - Он непрозрачен. Не разбирайте его, не конструируйте сами и не сохраняйте между изменениями схемы — его единственный контракт в том, что возврат курсора выдаёт следующую страницу.
total необязателен, и его отсутствие — это информация
total присутствует, когда мощность коллекции действительно известна: эндпоинт
выполнил настоящий подсчёт по тому же фильтру, что и страница. Он опускается,
когда подсчёт невозможен: проксирование во внешний источник, который не сообщает
количество, или поверхность только с limit без запроса подсчёта.
Отсутствующий total означает неизвестно. Он никогда не означает ноль и никогда
не синтезируется из длины страницы — total, равный data.length, рядом с
hasMore: true противоречил бы сам себе на каждой усечённой странице. Пишите
клиенты так, чтобы «137 результатов» отображалось только при наличии total, а
иначе — «показано 20» или обычные кнопки «дальше/назад».
total ортогонален механизму листания: курсорный эндпоинт тоже может знать свою
мощность и сообщать её.
Безопасный обход коллекции
let offset = 0;
const all = [];
for (;;) {
const res = await fetch(`${base}/v1/tables?limit=100&offset=${offset}`, { headers });
if (res.status === 429) { await backoff(res); continue; }
const page = await res.json();
all.push(...page.data);
if (!page.pagination.hasMore) break;
offset += page.pagination.limit;
}
Две вещи, которые этот цикл делает правильно, а наивный — нет: он завершается по
hasMore, а не по ожидаемому total, и трактует
429 как повтор, а не как конец коллекции.