Recibí esta pregunta en una entrevista. Un punto final de la API meteorológica puede devolver los datos meteorológicos para Pincode, ciudad, estado y país.
Dada una lista de API y sus prioridades, llámelas en paralelo y devuelva los datos de la API, que tiene alta prioridad .
APIs = [ { url: "api.weather.com/pin/213131", priority: 0 }, { url: "api.weather.com/state/california", priority: 2 }, { url: "api.weather.com/city/sanfrancisco", priority: 1 }, { url: "api.weather.com/country/usa", priority: 3 }, ];En la lista anterior, el orden de prioridad es pin > ciudad > estado > país. Esto significa llamar a todas las API en paralelo; si la API con Pincode devolvió los datos primero, resuelva la Promesa de inmediato; si no, debe resolver el siguiente de mayor prioridad.
Pensé en Promise.race(), pero eso no se considerará una prioridad. Luego pensé en esperar hasta que se agote el tiempo de espera, y debajo está mi código para el mismo. Simplemente espera hasta que se agote el tiempo de espera, y si el de alta prioridad se resuelve primero, entonces se resolverá la Promesa real. Después de un tiempo de espera específico, simplemente se resuelve con la primera respuesta de alta prioridad. Pero el entrevistador quiere que lo implemente sin tiempo de espera.
A continuación se muestra el código con un tiempo de espera. ¿Alguien sabe cómo implementarlo sin tiempo de espera y de manera más genérica?
function resolvePromiseWithPriority(APIS, timeout) { let PROMISES = APIS.sort((a, b) => a.priority - b.priority).map((api) => { return () => new Promise((resolve, reject) => { fetch(api.url) .then((data) => resolve(data)) .catch((err) => reject(err)); }); }); let priorities = [...APIS.map((item) => item.priority)]; let maxPriority = Math.min(...priorities); let minPriority = Math.max(...priorities); let results = []; let startTime = Date.now(); return new Promise((resolve, reject) => { PROMISES.forEach((promise, priority) => { promise() .then((data) => { results[priority] = data; let gap = (Date.now() - startTime) / 1000; if (gap > timeout) { // resolve the current high priority promise, if no promises resolved before the timeout, resolve the first one resolved. // If all promises are rejected, reject this promise. if (!results[minPriority] instanceof Error) resolve(resolve[minPriority]); for (let item of results) { if (!item instanceof Error) resolve(item); reject("No promises resolved !!"); } } else { if (priority === maxPriority) { // if the high priority promise gets it's data, resolve immediately. resolve(results[priority]); } if (priority < minPriority) { minPriority = priority; } } }) .catch((err) => { results[priority] = err; }); }); }); }En el entendido de que...
Dada una lista de API y sus prioridades, llámelas en paralelo y devuelva los datos de la API, que tiene una prioridad alta.
se expande a...
Dada una lista de API y sus prioridades, llámelas en paralelo y devuelva los datos de la API de mayor prioridad que entregue datos con éxito; si ninguna API tiene éxito, proporcione un error personalizado.
entonces no desea ni necesita un tiempo de espera, y el requisito se puede cumplir con tres métodos de matriz .sort() , .map() y .reduce() de la siguiente manera:
function resolvePromiseWithPriority(APIS) { return APIS .sort((a, b) => a.priority - b.priority); // sort the APIS array into priority order. .map(api => fetch(api.url)) // initialte all fetches in parallel. .reduce((cumulativePromise, fetchPromise) => cumulativePromise.catch(() => fetchPromise); // build a .catch().catch().catch()... chain }, Promise.reject()) // Rejected promise as a starter for the catch chain. .catch(() => new Error('No successful fetches')); // Arrive here if none of the fetches is successful. } Los .sort() y .map() se describen completamente mediante comentarios en el código.
La cadena catch formada por el método .reduce() funciona de la siguiente manera:
resolvePromiseWithPriority() entrega los datos de la recuperación exitosa; cualquier éxito de menor prioridad se ignora de hecho, ya que solo se consideraría en las capturas (omitidas);Esto es lo que se me ocurrió:
function resolvePromiseWithPriority(apis) { const promises = apis .sort((a, b) => a.priority - b.priority) .map(api => fetch(api.url).then(res => res.ok ? res.json() : Promise.reject(res.status)); const contenders = []; let prev = null; for (const promise of promises) { prev = Promise.allSettled([ prev ?? Promise.reject(), promise ]).then(([{status: prevStatus}]) => { if (prevStatus == 'rejected') return promise; // else ignore the promise }); contenders.push(prev); } return Promise.any(contenders); } Los contenders esperan todas las promesas prev (aquellas con mayor prioridad) y resuelven (al mismo resultado que la promise actual) una sola vez todas las rechazadas. Luego se pasan a Promise.any , que irá con el resultado del primer contendiente que se cumpla, o un AggregateError de todas las solicitudes fallidas en orden de prioridad.
Observe que un contendiente cumple con undefined si alguna de las promesas anteriores fulfill , pero esto realmente no importa: Promise.any iría con la primera. El código también funcionaría con Promise.allSettled([prev, promise]).then(() => promise) , pero imo verificar prevStatus hace que la intención sea un poco más clara.
El uso de Promise.allSettled (sobre prev.then(() => promise, () => promise) ) y Promise.any garantiza que el código no provoque rechazos no controlados.
Suponiendo que ya tiene solicitudes de promesas ordenadas por el campo de priority en la matriz sortedPromises , solo necesitamos obtener el resultado de la primera solicitud exitosa:
function getFirstSuccessfulResponse(sortedPromises) { let response = null; for (const promise of sortedPromises) { try { response = await promise; return response; } catch (err) { console.warn('Could not get data from the request') } } }