У меня есть маршрут API, который должен брать данные из двух источников, объединять данные в один объект и затем возвращаться. Проблема, с которой я сталкиваюсь, заключается в том, что я в основном застрял в асинхронном/ожидающем аду, и при нажатии на второй массив в блоке .then()
второй массив с именем
clone
возвращается []
. Как я могу сделать запрос API, объединить данные и вернуться к запрашивающей стороне по мере необходимости?
Получить код:
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;
}
Обработчик 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.info(clone);
clone
должен регистрироваться как массив объектов, таких как closest_array
, но вместо этого регистрируется как пустой массив. Это не совсем похоже на другие, казалось бы, повторяющиеся вопросы, потому что обычно их функция не требует отправки данных промиса обратно во внешний источник. Большинство вопросов связаны с передним концом вещей. Моя ситуация связана с API Express.js. Любая помощь будет оценена по достоинству.
@CherryDT, возможно, мне следовало упомянуть об этом в посте, но возвращаемые данные из вызова также являются пустым массивом.
В вашем коде есть недостаток (в разделе, показанном ниже). У вас есть невыполненное обещание, которое вы забыли дождаться или вернуть.
Когда вы регистрируетесь clone
, ни одна из асинхронных getProduct
операций еще не завершена, и ни один из элементов не был отправлен.
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.info(clone); // FLAW: clone is not ready yet.
Я бы поставил примерно так:
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.info(clone);
Немного схематично изменить element
таким, какой вы есть (я бы не стал), но таким образом все вызовы getProduct
находятся в полете вместе для максимальной эффективности. Promise.all
обрабатывает ожидание всех обещаний и помещает результат каждого в массив результатов, который затем можно ожидать как одно обещание, поскольку вызывающая функция является асинхронной.
Спасибо за ваш ответ, просто любопытно, как бы вы изменили element
в этом сценарии. Чем больше отзывов я получу, тем лучше
@ G.Rose Я думаю, что был удивлен, что вы создаете clone
, который является копией closest_array
, но со всеми теми же элементами (буквально теми же объектами), что заставляет меня задуматься, почему у вас вообще есть копия массива. С другой стороны, если бы каждый элемент клона был копировать каждого элемента в closest_array
, тогда кажется разумным сделать копию. В вашем случае я думаю, что вы просто добавляете несколько свойств к каждому элементу, поэтому я не уверен, что вам вообще нужен новый clone
новый массив. Трудно узнать ваши полные намерения только из того, что вы предоставили.
вы, вероятно, правы, я не думаю, что мне нужен клон. Сначала я установил его таким образом, потому что думал, что мне нужно нажать на мутировавший объект в foreach в массив клонов, чтобы избежать обещаний, но, конечно, это было не так.
Первоначальная спецификация промиса использовала .then()
, а новый синтаксис скрывает then с ожиданием. С точки зрения стиля имеет смысл выбрать стиль только один и придерживаться его.
В любом стиле есть небольшая проблема, связанная с созданием большого количества промисов в цикле. Функции итерации js (такие как map
и forEach
) принимают функции синхронный. Наиболее распространенный дизайн — создать набор промисов в синхронном цикле, а затем запустить их одновременно с Promise.all()
. Принимая во внимание обе идеи...
Вы можете (но не обязаны) переписать свой сетевой запрос следующим образом...
// 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
не разрешено на верхнем уровне; его можно использовать только внутри асинхронной функции. Здесь я придумаю имя и угадаю параметр
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.info(clones);
}
Вы регистрируете его еще до того, как к нему что-либо добавлено! getProduct является асинхронным.