При роботі з масивами в JavaScript метод .map
є відомим інструментом для ітерації по елементах, перетворюючи їх у нові значення. Але коли справа доходить до вкладених масивів з обіцянками (promises), .flatMap
може спростити ваш код, зробивши його чистішим та ефективнішим. У цій статті ми розглянемо різницю між .map
та .flatMap
, а також як елегантно працювати з вкладеними обіцянками.
У чому проблема?
Уявіть, що ви створюєте систему, де потрібно виконувати асинхронні операції над вкладеною структурою масивів. Наприклад:
- Зовнішній цикл: Ітерація по списку користувачів.
- Внутрішній цикл: Виконання операцій над завданнями кожного користувача.
Ось спрощений приклад:
const users = [
{ id: 1, name: 'Alice', tasks: [101, 102] },
{ id: 2, name: 'Bob', tasks: [201, 202] },
];
const processTask = (userId, taskId) =>
new Promise((resolve) =>
setTimeout(() => resolve(`Processed Task ${taskId} for User ${userId}`), 100)
);
Для кожного користувача ми хочемо одночасно обробити всі їх завдання.
Підхід за допомогою .map
Метод .map
відмінно підходить для створення нового масиву на основі результатів зворотного виклику (callback). Однак при роботі з вкладеними масивами він створює вкладені структури.
Ось як це виглядає у цьому випадку:
const tasks = users.map((user) =>
user.tasks.map((task) => processTask(user.id, task))
);
console.log(tasks);
/*
[
[Promise, Promise], // Завдання для Alice
[Promise, Promise] // Завдання для Bob
]
*/
// Щоб вирішити ці обіцянки, потрібно сплющити масив:
await Promise.all(tasks.flat());
Це працює, але вводить зайву вкладеність ([[Promise, Promise], [Promise, Promise]]
), що ускладнює читання коду.
Рішення за допомогою .flatMap
З допомогою .flatMap
ви можете відразу створити плоский масив обіцянок, усуваючи необхідність вручну сплющувати масив.
const tasks = users.flatMap((user) =>
user.tasks.map((task) => processTask(user.id, task))
);
console.log(tasks);
/*
[Promise, Promise, Promise, Promise]
*/
// Вирішуємо всі завдання
await Promise.all(tasks);
Цей підхід є чистішим і дозволяє уникнути проміжної вкладеності масивів.
Реальний приклад
Уявімо, що ви працюєте над проєктом, де:
- У вас є список контактів.
- Кожен контакт має кілька кампаній.
- Для кожної кампанії потрібно виконати API запит.
Ось загальна налаштування:
const contacts = [
{ id: 1, name: 'Alice', campaigns: [101, 102] },
{ id: 2, name: 'Bob', campaigns: [201, 202] },
];
const performApiCall = (contactId, campaignId) =>
new Promise((resolve) =>
setTimeout(() => resolve(`Processed Campaign ${campaignId} for Contact ${contactId}`), 100)
);
Використання .map
з вкладеним Promise.all
await Promise.all(
contacts.map((contact) =>
Promise.all(
contact.campaigns.map((campaign) => performApiCall(contact.id, campaign))
)
)
);
Хоча це працює, вкладені виклики Promise.all
ускладнюють читання коду.
Спрощення цієї логіки за допомогою .flatMap
робить код чистішим.
Оптимізована версія з .flatMap
await Promise.all(
contacts.flatMap((contact) =>
contact.campaigns.map((campaign) => performApiCall(contact.id, campaign))
)
);
Ця версія усуває зайву вкладеність і фокусується на створенні плоского масиву обіцянок.
Коли використовувати .map
vs .flatMap
Використовуйте .map
:
- Коли вам потрібно зберегти вкладену структуру.
- Наприклад, якщо ви хочете згрупувати завдання за користувачами у фінальному результаті:
const results = await Promise.all(
contacts.map((contact) =>
Promise.all(
contact.campaigns.map((campaign) => performApiCall(contact.id, campaign))
)
)
);
console.log(results);
// [
// [Result, Result], // Завдання для Alice
// [Result, Result] // Завдання для Bob
// ]
Використовуйте .flatMap
:
- Коли ви хочете отримати плоский масив обіцянок для обробки всього одразу.
- Це більш лаконічно і уникає зайвої вкладеності.
Роздуми щодо продуктивності
Використання пам'яті:
.map
з.flat()
створює проміжний вкладений масив, що споживає більше пам'яті..flatMap
поєднує операції в один крок, економлячи ресурси.
Читабельність:
.flatMap
веде до чистішого коду в тих випадках, коли вкладеність не потрібна.
Висновок
Під час роботи з масивами обіцянок, вибір між .map
і .flatMap
залежить від того, чи потрібно зберегти вкладеність. Якщо ваша мета — отримати плоский масив операцій (що є поширеним у багатьох асинхронних робочих процесах), то .flatMap
спрощує ваш код, роблячи його як ефективним, так і легким для читання.
Наступного разу, коли ви зіткнетеся з вкладеними масивами обіцянок, не забувайте про потужність .flatMap
, щоб спростити ваше рішення!
Перекладено з: Demystifying .map vs .flatMap in Nested Promises