Ожидание необязательных обещаний и сохраняемых значений

#javascript #promise #output

#javascript #обещание #вывод

Вопрос:

У меня есть пара асинхронных функций, которые получают и сохраняют значение, если оно еще не было предоставлено.

 if (!output.valueA) {
  output.valueA = await getValueA();
}
  
if (!output.valueB) {
  output.valueB = await getValueB();
}
  

Я знаю, что могу использовать Promise.all() для ожидания обоих этих вызовов асинхронно, но каков был бы наилучший способ сделать это, не зная, требуется ли вообще тот или иной или оба вызова, а затем сохранить результаты в правильных переменных?

Комментарии:

1. Откуда output берется? Или это не имеет отношения к задаче?

2. На самом деле это не имеет значения, просто знайте, что ValueA и ValueB могут быть или не быть уже заполнены к моменту вызова этого кода.

3. Знаете ли вы структуру output заранее или у нее каждый раз новые свойства?

4. Структура выходного объекта известна заранее

Ответ №1:

Вы можете создать массив со всеми обещаниями, отфильтровать его по наличию значения, а затем использовать Promise.all . Вам также понадобится сопоставление ключей свойств 1 к 1 с асинхронными функциями, которые использовались для получения значения для этого ключа:

 const fns = {
  "valueA": getValueA,
  "valueB": getValueB,
};
  
 const keys = Object.keys(fns);
// all keys

const missing = keys.filter((key) => !output[key]);
// keys with missing values

const promises = missing.map((key) => fns[key]());
// an array of promises

const results = await Promise.all(promises);
// promise results, obviously

for (const [ index, key ] of missing.entries()) { // using Array.prototype.entries
  output[key] = results[index];
}
  

Ответ №2:

Не сильно изменяя свой пример, вы могли бы использовать then для установки обратного вызова, который устанавливает свойство при разрешении. Сохраните результирующее обещание внутри массива, чтобы вы знали, когда все обещания будут выполнены.

 const promises = [];

if (!output.valueA) {
  promises.push(getValueA().then(value => output.valueA = value));
}
  
if (!output.valueB) {
  promises.push(getValueB().then(value => output.valueB = value));
}

await Promise.all(promises);
  

Вы можете использовать более динамичный подход, заранее определив свойства для проверки и их асинхронные средства получения. Примером может быть:

 const asyncGetters = [["valueA", getValueA], ["valueB", getValueB]];

await Promise.all(
  asyncGetters
    .filter(([prop]) => !output[prop])
    .map(async ([prop, asyncGetter]) => output[prop] = await asyncGetter())
);
  

Здесь filter действует как ваш if оператор, выбирая только те элементы, которые имеют ложное значение. map обязательно соберет обещания асинхронных map обратных вызовов, чтобы вы могли использовать await Promise.all() для ожидания их завершения.

Комментарии:

1. Вы также можете использовать promises.push((async () => output.valueA = await getValueA())()) для первого блока кода, однако лично я нахожу асинхронный IIFE немного загадочным в качестве аргумента функции.

Ответ №3:

 const output = {
  valueA : null,
  valueB: null
};

async function getValueA() {
  await new Promise(r => setTimeout(r, 3000));
  output.valueA = 2;
  return output.valueA;
}

async function getValueB() {
  await new Promise(r => setTimeout(r, 3000));
  output.valueB = 5;
  return output.valueB;
}

async function getAllValues() {
  const promises = [output.valueA || getValueA(), output.valueB || getValueB()];
  await Promise.all(promises);
  console.log(output.valueA);
  console.log(output.valueB);
}

getAllValues();  

Вы можете передать Promise.all массив с фактическими значениями вместо обещаний, и он просто немедленно разрешится. Нет необходимости проверять, что все они являются обещаниями. Попробуйте запустить мой код выше, но измените output.ValueA и ValueB на число для начала — оно немедленно разрешится.

[править]

прочитайте комментарий OPs и сделал это в иллюстративных целях:

 const output = {
  valueA : null,
  valueB: null
};

async function getValueA() {
  await new Promise(r => setTimeout(r, 3000));
  return 2;
}

async function getValueB() {
  await new Promise(r => setTimeout(r, 3000));
  return 5;
}

async function getAllValues() {
  const promises = [output.valueA || getValueA(), output.valueB || getValueB()];
  const [a, b] = await Promise.all(promises);
  output.valueA = a;
  output.valueB = b;
  console.log(output.valueA);
  console.log(output.valueB);
}

getAllValues();  

Вы можете использовать деструктурирование массива для получения результатов Promise.all

Комментарии:

1. Для этого требуется переопределять getValueA и getValueB (и все последующие аналогичные методы получения) для каждого нового значения output

2. Для этого требуется только, чтобы я предположил, что существуют функции, которые существуют в вопросе OPs.

3. Мне нравится этот ответ, но функции getValueA и getValueB существуют в отдельном модуле, поэтому было бы предпочтительнее устанавливать выходные значения на основе результата функций, а не внутри самих функций.