Розвінчання міфів: .map та .flatMap у вкладених обіцянках

При роботі з масивами в 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

Leave a Reply

Your email address will not be published. Required fields are marked *