錯誤處理

捕捉 listingsapi-js 錯誤類別、讀取平台錯誤代碼,並建立可根據 retryAfter 調整的退避機制。

SDK 擲出的所有錯誤都衍生自 ListingsAPIError。API 失敗時會擲出 APIError 或其子類別,無論平台是以非 2xx HTTP 狀態回報失敗,還是在 HTTP 200 回應本文中回報失敗。網路失敗(DNS 錯誤、連線遭拒、逾時)會擲出 APIConnectionError。您無須檢查 success 旗標或原始酬載:Promise 成功解析即表示呼叫成功。

錯誤繼承階層

Code
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

所有類別都從套件根目錄匯出:

TypeScript
import {ListingsAPIError,APIError,AuthenticationError,PermissionDeniedError,NotFoundError,ValidationError,RateLimitError,InternalServerError,APIConnectionError,} from 'listingsapi-js';

APIError 屬性

屬性型別說明
statusCodenumber | null失敗回應的 HTTP 狀態:401、429、500,或在錯誤出現在 200 回應本文中時為 200。用戶端驗證失敗時為 null。
codestring | null第一個平台錯誤代碼,e.g。若 API 有傳送錯誤代碼,則為 SY10005。
errorsApiErrorEntry[]所有錯誤項目,皆正規化為 { code, message, context }。
responseBodystring | null以字串表示的原始回應本文。
retryAfternumber | null僅適用於 RateLimitError。重試前需等待的秒數,由 Retry-After 標頭解析而得;若沒有該標頭,則為 null。
messagestring供人閱讀的摘要,後面附上錯誤項目(或原始本文)。
namestring具體類別名稱,e.g。"RateLimitError"。
TypeScript
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);}}

依型別捕捉錯誤

驗證身分失敗

TypeScript
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.');}}

資源不存在

TypeScript
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:

TypeScript
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');}}

驗證失敗

伺服器端驗證失敗時,錯誤會包含平台的錯誤項目:

TypeScript
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 狀態絕不表示「請自行檢查本文」:

酬載中的判斷依據擲出的錯誤型別
頂層 errors[],代碼為 SY90005 或 SY90001AuthenticationError
頂層 errors[],代碼為 SY90003PermissionDeniedError
頂層 errors[],代碼為 RATE_LIMITEDRateLimitError
任何其他頂層 errors[]APIError
含有 errors[] 或 success: false 的寫入結果ValidationError

對於寫入操作(POST 和 DELETE 輔助函式),SDK 會檢查回應中的每個結果物件;只要任一物件回報 success: false 或含有 errors[],就會擲出 ValidationError,並將 statusCode 設為 200。

TypeScript
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。
  • postType(位於 bulkPublish)必須是 ANNOUNCEMENT、EVENT、OFFER、COVID19、PRODUCT 其中之一。

這些本機錯誤具有 statusCode: null,您可藉此區分本機錯誤與伺服器端拒絕的情況。低階 createPost(body) 方法會直接傳送原始本文,不會執行這些檢查。

TypeScript
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 提示值,無此值時則採用指數遞增的延遲時間:

backoff.ts
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 選項設定。逾時的嘗試會和其他連線失敗一樣進行重試;只有最後一次失敗才會擲出錯誤:

TypeScript
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);}}