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
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 unknownTodas las clases se exportan desde la raíz del paquete:
import {ListingsAPIError,APIError,AuthenticationError,PermissionDeniedError,NotFoundError,ValidationError,RateLimitError,InternalServerError,APIConnectionError,} from 'listingsapi-js';Propiedades de 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);}}Capturar errores por tipo
Fallos de autenticación
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
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:
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:
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":
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.
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:
nameno debe estar vacío ni contener solo espacios en blanco, ylocationIdsno debe estar vacío.sitesdebe ser un subconjunto no vacío deGOOGLEyFACEBOOK.messagees 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.ctaTypeyctaUrldeben proporcionarse juntos, yctaTypedebe tener uno de estos valores:BOOK,ORDER,SHOP,LEARN_MORE,SIGN_UP,GET_OFFER.createEventycreateOffertambién requierentitle.postType(enbulkPublish) 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.
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:
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:
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);}}Recursos relacionados
- Configuración, las opciones
timeoutymaxRetries - Inicio rápido, ejemplo mínimo de manejo de errores
- Casos de uso, manejo de errores en situaciones reales con scripts completos