如何在数组方法链中展开 Promise 数组?

Posted

技术标签:

【中文标题】如何在数组方法链中展开 Promise 数组?【英文标题】:How Can I Unwrap an Array of Promises within a Chain of Array Methods? 【发布时间】:2020-11-06 09:38:31 【问题描述】:

我有几个映射链,其中一个需要对每个数组元素执行数据库操作,所以我使用的是异步等待。

const resultsAsPromises = arr
  .map(syncDouble)
  .map(asyncMapper)

如果它是链中的最后一项,这不是问题,因为我可以用 Promise.all 解开它

console.log('results', await Promise.all(resultsAsPromises))

但是,我还需要在之后执行其他同步操作,所以我希望在继续下一个 .map 之前解开 Promise 的值。

有没有办法做到这一点?我想也许只是制作一个提取映射器,比如

function extractPromiseValues(value) 
  return value.then(v => v);

会起作用,但可惜不行。

var arr = [1, 2, 3, 4, 5];
function timeout(i) 
  return new Promise((resolve) => 
    setTimeout(() => 
      return resolve(`number is $i`);
    , 1);
  );


function syncDouble(i) 
  return i * 2;


async function asyncMapper(i) 
  return await timeout(i)


function extractPromiseValues(value) 
  return value.then(v => v);

async function main() 
  const resultsAsPromises = arr
    .map(syncDouble)
    .map(asyncMapper)
//     .map(extractPromiseValues)
  console.log('results', await Promise.all(resultsAsPromises))


main();

如何在数组方法链中解开一组 Promise

【问题讨论】:

.then(v => v) 本质上是一个无操作(除了为每个承诺解决方案添加另一个延迟微滴)。 我认为问题在于是否有可能有一个 .map() 调用链,在某些时候,一个返回一个承诺数组,下一个返回一个值数组,这些值从那些承诺。在这种情况下,我不确定下面的答案是否解决了这个问题。想法? 意思是,arr.map(returnValAfter1sec).map(doubleValAfterThat)可以写成吗? (其中returnValAfter1sec 接受一个值并返回一个承诺,doubleValAfterThat 接受一个承诺并返回一个值)。 @GhassenLouhaichi 我相信我的编辑已经解决了您的解释。你同意吗? @Sphinx 不,你不能,因为.reduce() 方法的返回值仍然是一个承诺。无论您采用何种方法,都必须在链的该部分之前插入 await 【参考方案1】:

与其将标识函数传递给.then(),不如传递您的同步操作,或者在将async 函数中的承诺await 传递给您的同步操作之前:

function syncCapitalize(s) 
  return s.slice(0, 1).toUpperCase() + s.slice(1);


const resultsAsPromises = arr
  .map(syncDouble)
  .map(asyncMapper)
  .map(p => p.then(syncCapitalize)); // OR
//.map(async p => syncCapitalize(await p));

在你的例子中,这看起来像:

function timeout(i) 
  return new Promise(resolve => 
    setTimeout(() => 
      resolve(`number is $i`);
    , 1);
  );


function syncDouble(i) 
  return i * 2;


function asyncMapper(i) 
  return timeout(i);


function syncCapitalize(s) 
  return s.slice(0, 1).toUpperCase() + s.slice(1);


async function main() 
  const arr = [1, 2, 3, 4, 5];
  const resultsAsPromises = arr
    .map(syncDouble)
    .map(asyncMapper)
    .map(p => p.then(syncCapitalize)); // OR
  //.map(async p => syncCapitalize(await p));

  console.log('results', await Promise.all(resultsAsPromises));


main();

或者,如果我们将问题解释为Ghassen Louhaichi has,您可以使用TC39 pipeline operator (|>) proposal 使用以下选项之一编写链:

F# Pipelines Proposal

const results = arr
  .map(syncDouble)
  .map(asyncMapper)
  |> Promise.all
  |> await
  .map(syncCapitalize);

Smart Pipelines Proposal

const results = (arr
  .map(syncDouble)
  .map(asyncMapper)
  |> await Promise.all(#))
  .map(syncCapitalize);

不幸的是,除非您使用 Babel plugin,或者在其中一个提案被合并到官方 ECMAScript 规范中之前,您必须使用 await Promise.all(...) 包装链:

const results = (await Promise.all(arr
  .map(syncDouble)
  .map(asyncMapper)))
  .map(syncCapitalize);

最后,在您的完整示例的上下文中:

function timeout(i) 
  return new Promise(resolve => 
    setTimeout(() => 
      resolve(`number is $i`);
    , 1);
  );


function syncDouble(i) 
  return i * 2;


function asyncMapper(i) 
  return timeout(i);


function syncCapitalize(s) 
  return s.slice(0, 1).toUpperCase() + s.slice(1);


async function main() 
  const arr = [1, 2, 3, 4, 5];
  const results = (await Promise.all(arr
    .map(syncDouble)
    .map(asyncMapper)))
    .map(syncCapitalize);

  console.log('results', results);


main();

【讨论】:

精美!我们可以在某个地方对这些提案进行投票吗? 为了学究气,在你上次sn-p的最后,你只需要console.log('results', resultsAsPromises);,因为结果不再是promise了,事实上,数组的名字是有误导性的. @GhassenLouhaichi 本身没有“投票”的地方,但在GitHub issues section 中进行了很多积极的辩论,以解决用例、语法、语义、实现细节等。一般来说,一种风格得到的关注越多,委员会就越有可能选择它而不是另一种。不过,请确保您不要发布多余的问题来获得关注。 @GhassenLouhaichi 对,谢谢,我错过了。复制粘贴错误。 我觉得这真的很好。 .map(p => p.then(syncCapitalize)) 正是我想要的!我将不得不深入了解这个提议。包裹在Promise.all 中也一点也不差,也许就像要采取额外的步骤一样,但仍然很好。非常感谢帕特里克。

以上是关于如何在数组方法链中展开 Promise 数组?的主要内容,如果未能解决你的问题,请参考以下文章

将 JQuery Promise 数组转换为数组的 JQuery Promise 的最简洁方法是啥?

如何在 Promise 中获取数据数组 [重复]

如何在 mongodb 聚合中展开嵌套对象数组?

如何将 URL 中的数组作为参数传递给 Promise.all

如何在 NodeJS 中展开对象数组?

然后数组空承诺[重复]