Gestión de errores

Captura las clases de error de listingsapi-js, lee los códigos de error de la plataforma e implementa esperas progresivas que tengan en cuenta retryAfter.

Todos los errores que lanza el SDK derivan de ListingsAPIError. Los fallos de la API lanzan APIError o una de sus subclases, tanto si la plataforma los comunica mediante un estado HTTP fuera del rango 2xx como si los incluye en el cuerpo de una respuesta HTTP 200. Los fallos de red (errores de DNS, conexiones rechazadas, tiempos de espera agotados) lanzan APIConnectionError. Nunca tienes que inspeccionar los indicadores success ni los datos sin procesar: una promesa resuelta significa que la llamada se completó correctamente.

Jerarquía de errores

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

Todas las clases se exportan desde la raíz del paquete:

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

Propiedades de APIError

PropiedadTipoDescripción
statusCodenumber | nullEstado HTTP de la respuesta fallida: 401, 429, 500 o 200 cuando el error llegó en el cuerpo de una respuesta 200. null para los fallos de validación del lado del cliente.
codestring | nullPrimer código de error de la plataforma, e.g. SY10005, cuando la API envió uno.
errorsApiErrorEntry[]Todas las entradas de error, normalizadas a { code, message, context }.
responseBodystring | nullCuerpo de la respuesta sin procesar, como cadena de texto.
retryAfternumber | nullSolo para RateLimitError. Segundos que debes esperar antes de reintentar, extraídos de la cabecera Retry-After, o null cuando la cabecera no estaba presente.
messagestringResumen legible con las entradas de error (o el cuerpo sin procesar) añadidas al final.
namestringNombre de la clase concreta, 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);}}

Capturar errores por tipo

Fallos de autenticación

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

Recursos no encontrados

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

Límites de solicitudes

RateLimitError.retryAfter contiene el valor de Retry-After del servidor en segundos, o null cuando la cabecera no estaba presente:

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

Fallos de validación

Los fallos de validación del lado del servidor incluyen las entradas de error de la plataforma:

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

Errores en respuestas HTTP 200

La plataforma comunica algunos fallos mediante HTTP 200 e incluye datos de error en el cuerpo. El SDK inspecciona todas las respuestas y también lanza excepciones en estos casos, por lo que un estado 200 nunca significa "comprueba el cuerpo por tu cuenta":

Indicador en los datosSe lanza como
errors[] en el nivel superior con el código SY90005 o SY90001AuthenticationError
errors[] en el nivel superior con el código SY90003PermissionDeniedError
errors[] en el nivel superior con el código RATE_LIMITEDRateLimitError
Cualquier otro errors[] en el nivel superiorAPIError
Resultado de escritura con errors[] o success: falseValidationError

Para las operaciones de escritura (funciones auxiliares POST y DELETE), el SDK comprueba cada objeto de resultado de la respuesta; si alguno indica success: false o contiene errors[], lanza ValidationError con statusCode establecido en 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);}}

Validación del lado del cliente en las funciones auxiliares de publicación

Las funciones auxiliares de publicación (createAnnouncement, createEvent, createOffer y bulkPublish) validan sus datos localmente y lanzan ValidationError antes de realizar cualquier llamada de red:

  • name no debe estar vacío ni contener solo espacios en blanco, y locationIds no debe estar vacío.
  • sites debe ser un subconjunto no vacío de GOOGLE y FACEBOOK.
  • message es obligatorio; cuando es un mapa por sitio, cada sitio de destino debe tener una entrada que no esté vacía ni contenga solo espacios en blanco.
  • ctaType y ctaUrl deben proporcionarse juntos, y ctaType debe tener uno de estos valores: BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER.
  • createEvent y createOffer también requieren title.
  • postType (en bulkPublish) debe tener uno de estos valores: ANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT.

Estos errores locales tienen statusCode: null, lo que te permite distinguirlos de un rechazo del lado del servidor. El método de bajo nivel createPost(body) envía el cuerpo sin procesar y sin estas comprobaciones.

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

Reintentos integrados

El propio cliente reintenta las respuestas 429 y 5xx (respetando la cabecera Retry-After) y los fallos de red, dos veces de forma predeterminada. Los errores llegan a tu código solo después de agotar esos intentos. Consulta Configuración para conocer las opciones maxRetries y timeout.

Una estrategia de espera progresiva que tiene en cuenta retryAfter

Para los trabajos de larga duración que deban continuar después de agotar los reintentos integrados del cliente, envuelve las llamadas en un bucle de espera progresiva que dé prioridad a la indicación retryAfter del servidor y, en su ausencia, utilice intervalos de espera exponenciales:

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 y PermissionDeniedError no se reintentan deliberadamente: repetir esas solicitudes no puede dar resultado sin cambiar los datos de entrada o la clave.

Fallos de red y tiempos de espera agotados

Cualquier problema que impida recibir una respuesta (un fallo de DNS, una conexión rechazada o el agotamiento del tiempo de espera por solicitud) provoca que se lance APIConnectionError. El SDK nunca propaga los errores nativos TypeError o AbortError de fetch.

APIConnectionError no significa que la solicitud nunca haya llegado al servidor. Un fallo de DNS o de conexión rechazada sí detiene la solicitud antes de que llegue a la API, pero el agotamiento del tiempo de espera pertenece a la misma clase y la solicitud puede haber llegado y haberse completado después de que el cliente dejara de esperar. Trátalo como un resultado desconocido: puedes reintentar una lectura sin restricciones y, para una escritura, vuelve a leer el registro antes de repetirla, ya que la API no detecta las escrituras repetidas.

El tiempo de espera se aplica internamente mediante AbortSignal.timeout en cada intento, con un valor predeterminado de 240000 ms, configurable mediante la opción timeout del constructor. Un intento cuyo tiempo de espera se agota se reintenta como cualquier otro fallo de conexión; solo se lanza una excepción por el fallo final:

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