Skip to content

getAllContents() が取得中のコンテンツ減少時に終了しない可能性がある #116

Closed

Description

@gopalkriagg

概要

getAllContents() のページング中に、対象コンテンツが削除・非公開化されるなどして件数が減ると、処理が終了せず、リクエストを送り続ける可能性があります。

現行実装では、最初の limit: 0 リクエストで取得した totalCount を固定の終了条件として使っています。一方、各ページの取得後は、実際に取得できた件数だけ contents.length が増え、offset は常に 100 ずつ増えます。

そのため、最初の totalCount に達する前に空ページが返ると、contents.length < totalCount が永続的に真となります。

対象箇所:

endpoint,
queries: { ...queries, limit: 0 },
requestInit: customRequestInit,
});
let contents: (T & MicroCMSListContent)[] = [];
let offset = 0;
const sleep = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
while (contents.length < totalCount) {
const { contents: _contents } = (await makeRequest({
endpoint,
queries: { ...queries, limit, offset },
requestInit: customRequestInit,
})) as MicroCMSListResponse<T>;
contents = contents.concat(_contents);
offset += limit;
if (contents.length < totalCount) {
await sleep(1000); // sleep for 1 second before the next request
}
}

対象バージョン

  • microcms-js-sdk v3.5.0
  • main(commit cbd80f4623eed2b416ef61f817a59ead4d6106cc)

再現シナリオ

レスポンスを次の順序でモックすると再現できます。

  1. limit=0 の件数取得が totalCount: 101 を返す
  2. limit=100&offset=0 が 100 件を返す(累計 100 件)
  3. 残りの 1 件を削除または非公開化する
  4. limit=100&offset=100 が 0 件を返す
  5. 以降の offset=200, offset=300, ... も 0 件を返す

手順 4 以降は contents.length が 100 のまま、最初に取得した totalCount は 101 のままなので、ループを抜けられません。現行実装の sleep(1000) により、外部からタイムアウトまたは中断されるまで、1 秒間隔でリクエストが続きます。

実際の動作

  • getAllContents() の Promise が完了しない
  • 空ページへのリクエストが無制限に続く
  • SSG、ビルド、ワーカー、サーバーリクエストなどが外部タイムアウトまで停止する可能性がある

期待する動作

取得中に対象件数が変化しても、リクエスト回数が有界であり、Promise が必ず完了(resolve または reject)することを期待します。

例えば、次のいずれかで無限ループを防げると思います。

  • 取得したページが limit 未満なら終了する
  • 最初の totalCount から最大ページ数を決め、offset を基準にループを制限する

回帰テストとして、上記のレスポンス列で offset=100 の空ページ取得後に完了し、offset=200 をリクエストしないことを確認できると思います。

関連箇所

getAllContentIds() にも同様の終了条件があるため、同じ事象が発生する可能性があります。

const getAllContentIds = async ({
endpoint,
alternateField,
draftKey,
filters,
orders,
customRequestInit,
}: GetAllContentIdsRequest): Promise<string[]> => {
const limit = 100;
const defaultQueries: MicroCMSQueries = {
draftKey,
filters,
orders,
limit,
fields: alternateField ?? 'id',
depth: 0,
};
const { totalCount } = await makeRequest({
endpoint,
queries: { ...defaultQueries, limit: 0 },
requestInit: customRequestInit,
});
let contentIds: string[] = [];
let offset = 0;
const sleep = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
const isStringArray = (arr: unknown[]): arr is string[] =>
arr.every((item) => typeof item === 'string');
while (contentIds.length < totalCount) {
const { contents } = (await makeRequest({
endpoint,
queries: { ...defaultQueries, offset },
requestInit: customRequestInit,
})) as MicroCMSListResponse<Record<string, unknown>>;
const ids = contents.map((content) => content[alternateField ?? 'id']);
if (!isStringArray(ids)) {
throw new Error(
'The value of the field specified by `alternateField` is not a string.',
);
}
contentIds = [...contentIds, ...ids];
offset += limit;
if (contentIds.length < totalCount) {
await sleep(1000); // sleep for 1 second before the next request
}

Activity

  1. Sinhalite commented on Sep 17, 2026

    @Sinhalite
    Collaborator

    @gopalkriagg
    ご報告ありがとうございます。

    ご指摘いただいたとおり、取得中にコンテンツが削除・非公開化され、初回取得時の totalCount より実際の件数が少なくなると、リクエストが終了しない可能性がありました。

    getAllContents() と getAllContentIds() の終了条件を、取得済み件数ではなく、初回の totalCount と offset を基準に判定するよう修正しました。これにより、取得中に件数が減少した場合も、リクエスト回数が有限になります。

    修正は microcms-js-sdk@3.5.1 に含まれています。

    具体的な再現手順と修正案をご提示いただき、ありがとうございました。

  2. gopalkriagg commented on Sep 17, 2026

    @gopalkriagg
    Author

    @Sinhalite 早速修正し、リリースして頂きありがとうございました!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions