錯誤處理
捕捉 listingsapi-js 錯誤類別、讀取平台錯誤代碼,並建立可根據 retryAfter 調整的退避機制。
SDK 擲出的所有錯誤都衍生自 ListingsAPIError。API 失敗時會擲出 APIError 或其子類別,無論平台是以非 2xx HTTP 狀態回報失敗,還是在 HTTP 200 回應本文中回報失敗。網路失敗(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[],就會擲出 ValidationError,並將 statusCode 設為 200。
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) 方法會直接傳送原始本文,不會執行這些檢查。
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);}}