等待可选承诺并存储值

3

我有几个异步函数,如果值尚未提供,则获取和存储该值。

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

我知道可以使用 Promise.all() 异步等待这两个调用,但是如果不知道是否需要其中一个或两个调用,那么最好的方法是什么,并将结果存储在正确的变量中?


output 来自哪里?或者对于任务来说它是无关紧要的吗? - Parzh from Ukraine
其实这并不重要,只需要知道在调用此代码时,valueA和valueB可能已经被填充,也可能没有。 - DBPaul
你是否预先了解output的结构,还是它每次都有新属性? - Parzh from Ukraine
输出对象的结构是预先已知的。 - DBPaul
3个回答

3

您可以创建包含所有承诺的数组,根据存在值进行过滤,然后使用Promise.all。您还需要一个属性键到异步函数的一对一映射,用于获取该键的值:

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来设置一个回调来在解析时设置属性。将结果的Promise存储在数组中,以便您知道什么时候所有的Promise都已完成。

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);

您可以通过预先定义要检查的属性及其异步 getter 来使用更动态的方法。以下是一个示例:

代码如下:

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() 等待它们全部完成。


你也可以在第一个代码块中使用 promises.push((async () => output.valueA = await getValueA())()),但我个人认为异步 IIFE 作为函数参数有点晦涩难懂。 - 3limin4t0r

1

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传递一个包含实际值的数组,它会立即解析。不需要确保它们都是promise。尝试运行上面的代码,但将output.valueA和valueB更改为数字 - 它将立即解析。

[编辑]

阅读OP的评论并出于说明目的进行了以下操作:

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的结果

这需要对 getValueAgetValueB(以及所有后续类似的获取器)进行重新定义,以适应每个新值的 output - Parzh from Ukraine
这只需要我假设在 OP 的问题中存在的函数已经存在。 - TKoL
我喜欢这个答案,但是函数getValueAgetValueB存在于一个单独的模块中,所以最好根据函数的结果设置输出值,而不是在函数内部设置。 - DBPaul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接