Этот вопрос проверяет знание альтернативных подходов к работе с асинхронными операциями когда стандартные методы недоступны.
Для объединения результатов двух параллельных запросов без Promise.all можно использовать: счетчик завершения, async/await с отдельными await, Promise.race для контроля времени, или ручное создание промисов с отслеживанием состояния. Также подходят библиотеки как RxJS или кастомная реализация механизма ожидания.
Когда Promise.all недоступен, есть несколько способов协调 выполнения параллельных асинхронных операций.
javascript
function combineRequestsWithoutPromiseAll(requestA, requestB) {
return new Promise((resolve, reject) => {
let results = {};
let completed = 0;
let hasError = false;
function checkCompletion() {
completed++;
if (completed === 2 && !hasError) {
resolve(results);
}
}
requestA()
.then(resultA => {
results.A = resultA;
checkCompletion();
})
.catch(error => {
if (!hasError) {
hasError = true;
reject(error);
}
});
requestB()
.then(resultB => {
results.B = resultB;
checkCompletion();
})
.catch(error => {
if (!hasError) {
hasError = true;
reject(error);
}
});
});
}
// Использование
combineRequestsWithoutPromiseAll(fetchA, fetchB)
.then(({ A, B }) => {
console.log('Результаты:', A, B);
})
.catch(error => {
console.error('Ошибка:', error);
});javascript
async function combineWithAsyncAwait() {
try {
// Запускаем оба запроса параллельно
const promiseA = fetchA();
const promiseB = fetchB();
// Ждем результаты по отдельности
const resultA = await promiseA;
const resultB = await promiseB;
return { A: resultA, B: resultB };
} catch (error) {
console.error('Ошибка в одном из запросов:', error);
throw error;
}
}javascript
function customPromiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
promises.forEach((promise, index) => {
promise
.then(result => {
results[index] = result;
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch(error => {
reject(error);
});
});
});
}
// Для двух запросов
customPromiseAll([fetchA(), fetchB()])
.then(([resultA, resultB]) => {
console.log('Объединенный результат:', { A: resultA, B: resultB });
});javascript
async function combineWithRace() {
const promiseA = fetchA();
const promiseB = fetchB();
// Создаем таймаут для контроля времени выполнения
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
);
// Ждем завершения всех или таймаут
const resultA = await Promise.race([promiseA, timeout]);
const resultB = await Promise.race([promiseB, timeout]);
return { A: resultA, B: resultB };
}javascript
class RequestCombiner {
constructor() {
this.results = {};
this.pending = new Set();
}
addRequest(key, promise) {
this.pending.add(key);
return promise
.then(result => {
this.results[key] = result;
this.pending.delete(key);
return this.checkAllDone();
})
.catch(error => {
this.pending.delete(key);
throw error;
});
}
checkAllDone() {
if (this.pending.size === 0) {
return { ...this.results };
}
return null;
}
}
// Использование
const combiner = new RequestCombiner();
combiner.addRequest('A', fetchA());
combiner.addRequest('B', fetchB())
.then(combinedResult => {
if (combinedResult) {
console.log('Все запросы завершены:', combinedResult);
}
});javascript
import { forkJoin, from } from 'rxjs';
// Преобразуем промисы в Observable
const observableA = from(fetchA());
const observableB = from(fetchB());
forkJoin([observableA, observableB]).subscribe({
next: ([resultA, resultB]) => {
console.log('Результаты:', { A: resultA, B: resultB });
},
error: error => {
console.error('Ошибка:', error);
}
});Счетчик завершения - простой, но много boilerplate кода
Async/await - чистый и читаемый, но требует современный JavaScript
Кастомная реализация - гибкая, можно добавить дополнительную логику
RxJS - мощный, но требует дополнительной зависимости
Вывод: Без Promise.all можно использовать различные паттерны для координации параллельных запросов, от простого счетчика завершения до более сложных реактивных подходов. Выбор зависит от требований проекта и доступных инструментов.