エラー処理
listingsapi-jsのエラークラスをキャッチし、プラットフォームのエラーコードを読み取り、retryAfterを考慮したバックオフを実装します。
SDK がスローするすべてのエラーは、ListingsAPIError を継承しています。API の失敗では、プラットフォームが 2xx 以外の HTTP ステータスで報告する場合も、HTTP 200 の本文内で報告する場合も、APIError またはそのサブクラスがスローされます。ネットワークの失敗(DNS エラー、接続拒否、タイムアウト)では、APIConnectionError がスローされます。success フラグや生のペイロードを調べる必要はありません。Promise が正常に解決された場合、呼び出しは成功しています。
エラーの継承関係
ListingsAPIError base class for everything the SDK throws├── APIError any failure reported by the API│ ├── AuthenticationError 401, or invalid-token error payloads│ ├── PermissionDeniedError 403, or permission error payloads│ ├── NotFoundError 404, resource not found│ ├── ValidationError 400/422, mutations reporting success=false│ │ or errors[], and client-side payload checks│ ├── RateLimitError 429, carries retryAfter│ └── InternalServerError 5xx, retry a read; read back a write first└── APIConnectionError no response arrived: outcome unknownすべてのクラスはパッケージのルートからエクスポートされます。
import {ListingsAPIError,APIError,AuthenticationError,PermissionDeniedError,NotFoundError,ValidationError,RateLimitError,InternalServerError,APIConnectionError,} from 'listingsapi-js';APIError のプロパティ
import { ListingsAPI, APIError, APIConnectionError } from 'listingsapi-js'; const client = new ListingsAPI(); try {await client.fetchAllLocations({ first: 10 });} catch (error) {if (error instanceof APIError) { console.error(error.statusCode); // 401, 429, 500, or 200 for payload errors console.error(error.code); // first platform code, e.g. 'SY10005' console.error(error.errors); // [{ code, message, context }, ...] console.error(error.responseBody); // raw body text} else if (error instanceof APIConnectionError) { // No response arrived. A read is safe to retry; for a write, read the // record back before repeating it. console.error('No response from the API:', error.message);}}型別のエラーの捕捉
認証の失敗
import { AuthenticationError } from 'listingsapi-js'; try {await client.fetchAllLocations({ first: 1 });} catch (error) {if (error instanceof AuthenticationError) { console.error('Invalid API key. Rotate it under API Keys in the dashboard.');}}リソースが見つからない場合
import { NotFoundError } from 'listingsapi-js'; try {await client.fetchPremiumListings(99999999);} catch (error) {if (error instanceof NotFoundError) { console.error('Location not found.');}}レート制限
RateLimitError.retryAfter は、サーバーの Retry-After の値を秒単位で保持し、ヘッダーが存在しなかった場合は null を保持します。
import { RateLimitError } from 'listingsapi-js'; try {await client.fetchAllLocations({ first: 100 });} catch (error) {if (error instanceof RateLimitError) { const waitSeconds = error.retryAfter ?? 5; console.error('Rate limited, retry in ' + waitSeconds + 's');}}検証の失敗
サーバー側の検証の失敗には、プラットフォームのエラーエントリが含まれます。
import { ValidationError } from 'listingsapi-js'; try {await client.updateLocation({ id: 16808, phone: 'not-a-phone-number' });} catch (error) {if (error instanceof ValidationError) { for (const entry of error.errors) { console.error((entry.code ?? 'unknown') + ': ' + entry.message); }}}HTTP 200 レスポンス内のエラー
プラットフォームは、一部の失敗を HTTP 200 と本文内のエラーペイロードで報告します。SDK はすべてのレスポンスを検査し、これらの場合にもエラーをスローするため、200 ステータスであっても「自分で本文を確認する」必要はありません。
書き込み(POST および DELETE ヘルパー)では、SDK はレスポンス内のすべての結果オブジェクトを確認し、いずれかが success: false を報告するか、errors[] を含む場合、statusCode を 200 に設定した ValidationError をスローします。
import { ValidationError } from 'listingsapi-js'; try {const result = await client.archiveLocations([16808]);console.log('Archived:', result);} catch (error) {if (error instanceof ValidationError) { // The API answered 200 but rejected the mutation console.error(error.statusCode); // 200 console.error(error.message);}}投稿ヘルパーでのクライアント側の検証
投稿ヘルパー(createAnnouncement、createEvent、createOffer、bulkPublish)はペイロードをローカルで検証し、検証に失敗した場合はネットワーク呼び出しを行う前に ValidationError をスローします。
nameは空文字列や空白のみであってはならず、locationIdsは空であってはなりません。sitesは、GOOGLEとFACEBOOKからなる集合の空でない部分集合である必要があります。messageは必須です。サイトごとのマップである場合、対象のすべてのサイトに、空文字列でも空白のみでもないエントリが必要です。ctaTypeとctaUrlは一緒に指定する必要があり、ctaTypeはBOOK、ORDER、SHOP、LEARN_MORE、SIGN_UP、GET_OFFERのいずれかである必要があります。createEventとcreateOfferでは、さらにtitleが必要です。bulkPublishのpostTypeは、ANNOUNCEMENT、EVENT、OFFER、COVID19、PRODUCTのいずれかである必要があります。
これらのローカルエラーには statusCode: null があるため、サーバー側での拒否と区別できます。低レベルの createPost(body) メソッドは、これらの検証を行わずに生のリクエストボディを送信します。
import { ListingsAPI, ValidationError } from 'listingsapi-js'; const client = new ListingsAPI(); try {await client.createAnnouncement({ name: 'Grand opening', locationIds: [16808], message: 'Acme Dental is now open in Midtown!', ctaType: 'BOOK', // ctaUrl missing: throws before any request is sent});} catch (error) {if (error instanceof ValidationError && error.statusCode === null) { console.error(error.message); // Invalid post: ctaType and ctaUrl must be provided together}}組み込みの再試行
クライアント自体が、429 および 5xx レスポンス(Retry-After ヘッダーに従います)とネットワークの失敗に対して、デフォルトで 2 回再試行します。これらの再試行をすべて使い切った後でのみ、エラーがコードに渡されます。maxRetries および timeout オプションについては、設定 を参照してください。
retryAfter を考慮したバックオフの実装例
クライアントの組み込みの再試行を使い切った後も継続する必要がある長時間実行ジョブでは、サーバーの retryAfter の指定を優先し、指定がなければ待機時間を指数関数的に増やすバックオフループで呼び出しを囲みます。
import {ListingsAPI,RateLimitError,InternalServerError,APIConnectionError,} from 'listingsapi-js'; const client = new ListingsAPI(); async function withBackoff<T>(fn: () => Promise<T>,maxAttempts = 5,): Promise<T> {for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (err) { const retryable = err instanceof RateLimitError || err instanceof InternalServerError || err instanceof APIConnectionError; if (!retryable || attempt === maxAttempts) throw err; let delayMs = 500 * 2 ** (attempt - 1); if (err instanceof RateLimitError && err.retryAfter !== null) { delayMs = err.retryAfter * 1000; } await new Promise((resolve) => setTimeout(resolve, delayMs)); }}throw new Error('unreachable');} const page = await withBackoff(() => client.fetchAllLocations({ first: 25 }));NotFoundError、ValidationError、AuthenticationError、PermissionDeniedError は、意図的に再試行の対象から除外されています。入力またはキーを変更せずにこれらのリクエストを繰り返しても、成功しないためです。
ネットワークの失敗とタイムアウト
レスポンスの受信を妨げる事象(DNS の失敗、接続拒否、リクエストごとのタイムアウトの発生)があると、APIConnectionError がスローされます。SDK は、fetch のネイティブな TypeError や AbortError をそのまま呼び出し元に伝播させることはありません。
APIConnectionError は、リクエストがサーバーに届かなかったことを意味しません。DNS の失敗や接続拒否の場合は API に到達する前に処理が停止していますが、タイムアウトにも同じクラスが使われ、クライアントが待機を打ち切った後にリクエストが到達し、処理が完了している可能性があります。結果は不明として扱ってください。読み取りはそのまま再試行できますが、API は書き込みの重複を検出しないため、書き込みを繰り返す前にはレコードを読み取って確認してください。
タイムアウトは、各試行で内部的に AbortSignal.timeout を使って適用され、デフォルトは 240000 ms で、timeout コンストラクターオプションで設定できます。タイムアウトした試行は、他の接続の失敗と同様に再試行され、最後の失敗のみがエラーとして送出されます。
import { ListingsAPI, APIConnectionError } from 'listingsapi-js'; const client = new ListingsAPI({ timeout: 15000 }); try {await client.fetchAllLocations({ first: 10 });} catch (error) {if (error instanceof APIConnectionError) { console.error('Network failure or timeout:', error.message);}}