【问题标题】:How to use async-await with Promise.all()?如何将 async-await 与 Promise.all() 一起使用?
【发布时间】:2019-02-09 20:04:09
【问题描述】:

目前我正在使用下面的代码来获取使用异步等待的几个 Promise 的结果:

let matchday = await createMatchday(2018, 21, [/*9 matches of matchday*/]);
//Further calculations

async function createMatchday(seasonNr, matchdayNr, matches) {
  let md = new Matchday(seasonNr, matchdayNr, matches);
  await md.getStandings(seasonNr, matchdayNr);
  return md;
}

class Matchday {
  constructor(seasonNr, matchdayNr, matches) {
    this.seasonNr = seasonNr;
    this.matchdayNr = matchdayNr;
    this.matches = matches;
  }

  async getStandings(seasonNr, matchdayNr) {
    let promiseArr = [];
    promiseArr.push(makeHttpRequestTo(`http://externService.com/standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`);
    promiseArr.push(makeHttpRequestTo(`http://externService.com/homestandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
    promiseArr.push(makeHttpRequestTo(`http://externService.com/awaystandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));
    promiseArr.push(makeHttpRequestTo(`http://externService.com/formstandings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`));

    let resulArr = await Promise.all(promiseArr);
    this.standings = resultArr[0];
    this.homeStandings = resultArr[1];
    this.awayStandings = resultArr[2];
    this.formStandings = resultArr[3];
  }
}

function makeHttpRequest(url) {
  return new Promise((resolve, reject) => {
    //AJAX httpRequest to url
    resolve(httpRequest.responseText);
  }
}

这实际上是读取多个 Promise 值的最佳方法吗,其中 Promise 不需要等待对方结束,而是使用 Promise.all() 同时工作还是有更好的方法制作例如多个httpRequests,因为这看起来很重复?

【问题讨论】:

  • 您当前的代码确实同时发出多个请求(假设 Promise.resolves 被替换为发出请求的东西)
  • 我只是想知道这是不是这样写的方式,因为它看起来很不方便......
  • 你说的“更好”是什么意思?有什么要求?
  • 你的意思是代码看起来太重复了?我同意,但是没有看到真实代码,很难说有什么可以改进的
  • 添加了“真实”代码...

标签: javascript async-await


【解决方案1】:

您的 URL 都遵循相同的模式,因此您可以通过 mapping 一组 ['', 'home', 'away', 'form'] 到 URL 来大大减少您的代码。然后,map那些通过makeHttpRequestTo到Promises的URL,然后你可以将等待的结果解构到this.属性中:

async getStandings(seasonNr, matchdayNr) {
  const urls = ['', 'home', 'away', 'form']
    .map(str => `http://externService.com/${str}standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`);
  const promiseArr = urls.map(makeHttpRequestTo);
  [
    this.standings,
    this.homeStandings,
    this.awayStandings,
    this.formStandings
  ] = await Promise.all(promiseArr);
}

单独填充每个属性,而不是等待所有响应返回:

async getStandings(seasonNr, matchdayNr) {
  ['', 'home', 'away', 'form']
    .forEach((str) => {
      const url = `http://externService.com/${str}standings?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`;
      makeHttpRequestTo(url)
        .then((resp) => {
          this[str + 'Standings'] = resp;
        });
    });
}

【讨论】:

  • 谢谢。认为这应该可行,而且看起来不那么重复:)
  • 我认为这不是正确的方法,因为每个异步操作都不依赖于之前或之后的操作。这样我们在移动到另一个函数之前等待一个函数完成它的执行。这不是必需的。这种方式使它成为异步/等待地狱。
  • @AdeelImran Promise.all 表示每个异步调用将立即发出,并在所有响应返回后解决。它不是串行等待,而是并行等待。
  • 是的,但是为了得到 Promise1 的结果,我们必须等到所有 PromiseN 都被执行,这不应该是这种情况。
  • 阿迪尔是对的。在这种情况下,它们可以自己处理。我还是更喜欢CertainPerformance 编写代码的方式。 Adeel 的代码写得更好会是更好的方法。
【解决方案2】:

如果您不想在继续执行流程之前等待所有请求完成,您可以将类的属性设置为 promise:

class Matchday {
  constructor(seasonNr, matchdayNr, matches) {
    this.seasonNr = seasonNr;
    this.matchdayNr = matchdayNr;
    this.matches = matches;
    ['standings', 'homeStandings', 'awayStandings', 'formStandings'].forEach(propertyName => {
      let url = `http://externService.com/${propertyName.toLowerCase()}`
        + `?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`
      this[propertyName] = makeHttpRequestTo(url)
    });
  }
}

使用以下sn-p进行测试

class Matchday {
  constructor(seasonNr, matchdayNr, matches) {
    this.seasonNr = seasonNr;
    this.matchdayNr = matchdayNr;
    this.matches = matches;
    ['standings', 'homeStandings', 'awayStandings', 'formStandings'].forEach(propertyName => {
      let url = `http://externService.com/${propertyName.toLowerCase()}`
        + `?seasonNr=${seasonNr}&matchdayNr=${matchdayNr}`
      this[propertyName] = makeHttpRequestTo(url)
    });
  }
}

/**************************************
 * Test harness
 **************************************/
 
function makeHttpRequestTo(url) {
  // Fake an AJAX httpRequest to url
  const requested_resource = url.match('^.*\/\/.*\/([^?]*)')[1];
  const fake_response_data = 'data for ' + url.match('^.*\/\/.*\/(.*)$')[1];
  let delay = 0;
  let response = '';
  switch (requested_resource) {
    // To make it interesting, let's give the 'standings' resource 
    // a much faster response time
    case 'standings':
      delay = 250;
      break;
    case 'homestandings':
      delay = 2000;
      break;
    case 'awaystandings':
      delay = 3000;
      break;
    case 'formstandings':
      delay = 4000; // <== Longest request is 4 seconds
      break;
    default:
      throw (util.format('Unexpected requested_resource: %s', requested_resource));
  }
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(fake_response_data), delay);
  });
}

async function testAccessingAllProperties() {
  const testId = "Test accessing all properties";
  console.log('\n%s', testId);
  console.time(testId)
  let md = new Matchday(2018, 21, []);
  console.log(await md.standings);
  console.log(await md.homeStandings);
  console.log(await md.awayStandings);
  console.log(await md.formStandings);
  console.timeEnd(testId)
}

async function testAccessingOnlyOneProperty() {
  const testId = `Test accessing only one property`;
  console.log('\n%s', testId);
  console.time(testId)
  let md = new Matchday(2018, 21, []);
  console.log(await md.standings);
  console.timeEnd(testId)
}

async function all_tests() {
  await testAccessingAllProperties();
  await testAccessingOnlyOneProperty();
}

all_tests();

结论

上面的sn-p表明执行时间没有被未访问的属性惩罚。并且访问所有属性的执行时间并不比使用promise.all差。

您只需要记住在访问这些属性时使用await

【讨论】:

  • 嘿罗宾,答案有点晚,但比其他人更有意义。很有启发,非常感谢!
【解决方案3】:

回答,不,您不应该阻止其他 XHR 或任何不相互依赖的 I/O 请求。我会这样写你的函数;

const getFavourites = async () => {
  try {
    const result = await Promise.resolve("Pizza");
    console.log("Favourite food: " + result);
  } catch (error) {
    console.log('error getting food');
  }
  try {
    const result = await Promise.resolve("Monkey");
    console.log("Favourite animal: " + result);
  } catch (error) {
    console.log('error getting animal');
  }
  try {
    const result = await Promise.resolve("Green");
    console.log("Favourite color: " + result);
  } catch (error) {
    console.log('error getting color');
  }
  try {
    const result = await Promise.resolve("Water");
    console.log("Favourite liquid: " + result);
  } catch (error) {
    console.log('error getting liquid');
  }
}

getFavourites();

这样每个异步函数都会被一次调用,并且没有异步操作会阻塞其他操作。

【讨论】:

  • 有一篇很棒的文章,我想和我的回答一起分享,我觉得这对我很有帮助medium.freecodecamp.org/…
  • 取决于您将要执行的异步操作的多样性(如果它们相同)。您可以有一个通用方法并在循环中传递参数。函数本身是 async/await 的地方
  • @IceRevenge 请注意,此实现将等待 serial 中的所有请求,而不是并行。例如。如果每个 API 调用需要 400 毫秒,并且有 4 个请求要发出,那么这将需要 1600 毫秒,而不是 400 毫秒。
【解决方案4】:

要创建Promise,您需要调用 new Promise((resolve, reject) => { return "Pizza"; })

你做对了

如果您愿意,可以使用数组(及其函数如map 等...)来缩短代码,但不会提高其性能

【讨论】:

  • "要创建Promise,您需要调用new Promise((resolve, reject) =&gt; { return "Pizza"; })"
  • 适用于虚拟代码,但可以肯定 OP 的真实代码看起来不同。 (另外,这里不需要Promise 构造函数)
  • 是的,如果你想要一份披萨的承诺,我想不出更好的办法
  • 查看 OP 的 Promise.resolve - 它比 new Promise(... 好得多
  • @CertainPerformance 是对的。您只需拨打Promise.resolve,编辑我的答案即可创建承诺
猜你喜欢
  • 2020-09-24
  • 2014-03-13
  • 2017-07-02
  • 1970-01-01
  • 2017-11-05
  • 2019-04-15
  • 2014-06-19
  • 2018-01-14
  • 2018-09-12
相关资源
最近更新 更多