Tengo una ruta api que necesita tomar datos de dos fuentes, fusionar los datos en un solo objeto y luego regresar. El problema que tengo es que básicamente estoy atrapado en async/await hell y cuando empujo a una segunda matriz dentro del bloque .then() , la segunda matriz llamada clone devuelve [] . ¿Cómo puedo hacer una solicitud de API, fusionar los datos y regresar al solicitante según sea necesario?
Obtener código:
export default async function getProduct(product_id) { const product = await fetch( `${process.env.PRIVATE_APP_URL}/products/${product_id}.json`, { method: "GET", headers: { "Content-Type": "application/json", }, } ).then((result) => { return result.json(); }); return product.product; }Controlador de API:
const recharge_subscription_res = await rechargeAPI( "GET", `https://api.rechargeapps.com/subscriptions?customer_id=${recharge_customer.id}` ); const closest_array = recharge_subscription_res.subscriptions.filter( (e) => e.next_charge_scheduled_at == closest_date ); let clone = []; closest_array.forEach((element) => { getProduct(element.shopify_product_id).then((product) => { element.shopify_product_handle = product.handle; element.shopify_product_image_url = product.image.src; clone.push(element); }); }); console.log(clone); clone debe registrarse como una matriz de objetos como la matriz más closest_array , pero en su lugar se registra como una matriz vacía. Esto no es exactamente como las otras preguntas aparentemente duplicadas porque, por lo general, su característica no requiere enviar los datos de la promesa a una fuente externa. La mayoría de las preguntas están relacionadas con la parte delantera de las cosas. Mi situación es con una API Express.js. Cualquier ayuda sería apreciada.
Su código tiene una falla (en la sección que se muestra a continuación). Tienes una promesa pendiente que olvidaste esperar o regresar. Cuando registra clone , ninguna de las operaciones getProduct asincrónicas se ha completado todavía y ninguno de los elementos se ha enviado.
let clone = []; closest_array.forEach((element) => { getProduct(element.shopify_product_id).then((product) => { element.shopify_product_handle = product.handle; element.shopify_product_image_url = product.image.src; clone.push(element); }); // FLAW: dangling .then }); console.log(clone); // FLAW: clone is not ready yet.Yo lo configuraría más así:
let clone = await Promise.all(closest_array.map((element) => getProduct(element.shopify_product_id).then((product) => { element.shopify_product_handle = product.handle; element.shopify_product_image_url = product.image.src; return element; }) )); console.log(clone); Es un poco incompleto modificar el element de la forma en que lo hace (yo no lo haría), pero de esta manera las llamadas a getProduct están todas en vuelo juntas para lograr la máxima eficiencia. Promise.all maneja esperando todas las promesas y colocando el resultado de cada una en una matriz de resultados, que luego puede esperar como una sola promesa ya que la función de llamada es asíncrona.
La especificación de promesa original usaba .then() , y la nueva sintaxis oculta entonces con await. En cuanto al estilo, tiene sentido elegir solo un estilo e ir con él.
En cualquier estilo, hay un pequeño desafío que tiene que ver con la creación de muchas promesas en un bucle. Las funciones de iteración js (como map y forEach ) toman funciones sincrónicas . El diseño más común es crear una colección de promesas en un ciclo síncrono y luego ejecutarlas simultáneamente con Promise.all() . Teniendo en cuenta ambas ideas...
Podría (pero no tiene que hacerlo) reescribir su solicitud de red de esta manera...
// since we decorated "async" let's use await... export default async function getProduct(product_id) { const url = `${process.env.PRIVATE_APP_URL}/products/${product_id}.json`; const options = { method: "GET", headers: { "Content-Type": "application/json" }}; const result = await fetch(url, options); const product = await result.json(); return product.product; } await no está permitido en el nivel superior; solo se puede usar dentro de una función asíncrona. Toma, inventaré un nombre y adivinaré el parámetro.
async function rechargeAndLookupProduct(recharge_customer) { const base = 'https://api.rechargeapps.com/subscriptions'; const query = `customer_id=${recharge_customer.id}`; const recharge_subscription_res = await rechargeAPI("GET",`${base}?${query}`); const closest_array = recharge_subscription_res.subscriptions.filter(e => e.next_charge_scheduled_at == closest_date ); // here's the important part: collect promises synchronously // execute them together with Promise.all() const promises = closest_array.map(element => { return getProduct(element.shopify_product_id) }); const allProducts = await Promise.all(promises); // allProducts will be an array of objects that the promises resolved to const clones = allProducts.map((product, i) => { // use Object.assign so we'll really have a "clone" let closest = Object.assign({}, closest_array[i]); closest.shopify_product_handle = product.handle; closest.shopify_product_image_url = product.image.src; return closest; }); // if I didn't make any typos (which I probably did), then // clones ought to contain the result you expect console.log(clones); }