Penanganan Error

Tangani kelas error listingsapi-js, baca kode error platform, dan terapkan penundaan percobaan ulang yang memperhitungkan retryAfter.

Setiap kesalahan yang dilempar SDK merupakan turunan dari ListingsAPIError. Kegagalan API melempar APIError atau salah satu subkelasnya, baik ketika platform melaporkannya sebagai status HTTP non-2xx maupun di dalam isi respons HTTP 200. Kegagalan jaringan (kesalahan DNS, koneksi ditolak, batas waktu terlampaui) melempar APIConnectionError. Anda tidak perlu memeriksa penanda success atau payload mentah: promise yang terpenuhi berarti pemanggilan berhasil.

Hierarki kesalahan

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

Semua kelas diekspor dari akar paket:

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

Properti APIError

PropertiTipeDeskripsi
statusCodenumber | nullStatus HTTP dari respons yang gagal: 401, 429, 500, atau 200 jika kesalahan diterima di dalam isi respons 200. null untuk kegagalan validasi di sisi klien.
codestring | nullKode kesalahan pertama dari platform, e.g. SY10005, jika API mengirimkannya.
errorsApiErrorEntry[]Semua entri kesalahan, dinormalisasi menjadi { code, message, context }.
responseBodystring | nullIsi respons mentah sebagai string.
retryAfternumber | nullHanya untuk RateLimitError. Waktu tunggu dalam detik sebelum mencoba ulang, diurai dari header Retry-After, atau null jika header tersebut tidak ada.
messagestringRingkasan yang mudah dibaca dengan entri kesalahan (atau isi respons mentah) ditambahkan di akhir.
namestringNama kelas konkret, 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);}}

Menangkap kesalahan berdasarkan tipe

Kegagalan autentikasi

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

Sumber daya yang tidak ditemukan

TypeScript
import { NotFoundError } from 'listingsapi-js'; try {await client.fetchPremiumListings(99999999);} catch (error) {if (error instanceof NotFoundError) {  console.error('Location not found.');}}

Batas laju permintaan

RateLimitError.retryAfter menyimpan nilai Retry-After dari server dalam detik, atau null jika header tersebut tidak ada:

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

Kegagalan validasi

Kegagalan validasi di sisi server menyertakan entri kesalahan dari platform:

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

Kesalahan di dalam respons HTTP 200

Platform melaporkan beberapa kegagalan dengan HTTP 200 dan payload kesalahan di dalam isi respons. SDK memeriksa setiap respons dan juga melempar kesalahan untuk kegagalan tersebut, sehingga status 200 tidak pernah berarti "periksa sendiri isi responsnya":

Indikator dalam payloadDilempar sebagai
errors[] di tingkat teratas dengan kode SY90005 atau SY90001AuthenticationError
errors[] di tingkat teratas dengan kode SY90003PermissionDeniedError
errors[] di tingkat teratas dengan kode RATE_LIMITEDRateLimitError
errors[] lainnya di tingkat teratasAPIError
Hasil penulisan dengan errors[] atau success: falseValidationError

Untuk operasi penulisan (fungsi pembantu POST dan DELETE), SDK memeriksa setiap objek hasil dalam respons; jika ada yang melaporkan success: false atau menyertakan errors[], SDK melempar ValidationError dengan statusCode diatur ke 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);}}

Validasi di sisi klien pada fungsi pembantu post

Fungsi pembantu post (createAnnouncement, createEvent, createOffer, dan bulkPublish) memvalidasi payload secara lokal dan melempar ValidationError sebelum melakukan pemanggilan jaringan apa pun:

  • name tidak boleh kosong atau hanya berisi spasi, dan locationIds tidak boleh kosong.
  • sites harus berupa himpunan bagian yang tidak kosong dari GOOGLE dan FACEBOOK.
  • message wajib diisi; jika berupa pemetaan per situs, setiap situs yang ditargetkan harus memiliki entri yang tidak kosong dan tidak hanya berisi spasi.
  • ctaType dan ctaUrl harus diberikan bersamaan, dan ctaType harus berupa salah satu dari BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER.
  • createEvent dan createOffer juga memerlukan title.
  • postType (pada bulkPublish) harus berupa salah satu dari ANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT.

Kesalahan lokal ini memiliki statusCode: null, yang memungkinkan Anda membedakannya dari penolakan di sisi server. Metode tingkat rendah createPost(body) mengirim isi permintaan mentah tanpa pemeriksaan ini.

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}}

Percobaan ulang bawaan

Klien secara otomatis mencoba ulang respons 429 dan 5xx (dengan mematuhi header Retry-After) serta kegagalan jaringan, secara default sebanyak 2 kali. Kesalahan baru diteruskan ke kode Anda setelah semua percobaan tersebut habis. Lihat Konfigurasi untuk opsi maxRetries dan timeout.

Contoh penerapan jeda percobaan ulang yang memperhitungkan retryAfter

Untuk tugas berdurasi panjang yang perlu terus berjalan setelah percobaan ulang bawaan klien habis, bungkus pemanggilan dalam perulangan dengan jeda yang mengutamakan petunjuk retryAfter dari server dan menggunakan jeda eksponensial jika petunjuk tersebut tidak tersedia:

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, dan PermissionDeniedError sengaja tidak dicoba ulang: mengulangi permintaan tersebut tidak akan berhasil tanpa mengubah masukan atau kunci.

Kegagalan jaringan dan batas waktu terlampaui

Apa pun yang menghalangi diterimanya respons (kegagalan DNS, koneksi ditolak, atau batas waktu per permintaan terlampaui) melempar APIConnectionError. SDK tidak pernah meneruskan TypeError atau AbortError bawaan dari fetch.

APIConnectionError tidak berarti permintaan tidak pernah sampai ke server. Kegagalan DNS atau koneksi ditolak memang menghentikan permintaan sebelum mencapai API, tetapi batas waktu terlampaui menggunakan kelas yang sama, dan permintaan mungkin telah tiba serta selesai diproses setelah klien berhenti menunggu. Perlakukan kondisi ini sebagai hasil tidak diketahui: Anda dapat mencoba ulang operasi baca secara bebas, dan untuk operasi tulis, baca kembali rekaman sebelum mengulanginya karena API tidak mendeteksi penulisan berulang.

Batas waktu diterapkan secara internal menggunakan AbortSignal.timeout pada setiap percobaan, secara default sebesar 240000 ms, dan dapat dikonfigurasi melalui opsi konstruktor timeout. Percobaan yang melampaui batas waktu dicoba ulang seperti kegagalan koneksi lainnya; hanya kegagalan terakhir yang dilempar:

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