【问题标题】:Converting a javascript loop to be asynchronous and functional将 javascript 循环转换为异步和函数式
【发布时间】:2020-06-29 16:45:54
【问题描述】:

我正在尝试使以下代码更具功能性和异步性,但运气不佳!

所以 api 调用一次只能获取 5 个 id,这就是我创建 splitListOfIds 并循环遍历它的原因。第二个 api 调用依赖于第一个 api 调用

下面的代码可以工作,但速度很慢,但我在这段代码中遇到 ESLINT 错误,并希望使其更具功能性和异步性。我一直在尝试使用 Promises,但不知道该怎么做。理想情况下希望摆脱for 循环。

const splitListOfIds = [
    [1,2,3,4,5],
    [6,7,8,9,10]
]

const allPolicies = []

for (let i = 0; i < splitListOfIds.length; i++) {
    const people = await peopleApi.getPeople(splitListOfIds[i])
    const policyIds = people.map(p => p.policyId)
    const policies = await policyApi.getPolicyDetails(policyIds)
    allPolicies.push(policies)
}

【问题讨论】:

  • (顺便说一句,你已经在使用 Promise)
  • 如果你在使用await,你已经在使用 Promises。您想摆脱 for 循环的任何具体原因?
  • “更异步”是什么意思?
  • 你遇到了什么 linter 错误?

标签: javascript ecmascript-6 functional-programming es6-promise


【解决方案1】:

例如,我会使用 RxJs 和 Observable 以一种优雅的方式来做到这一点

// Emit people ids as a sequence
const source = Rx.Observable.from([1,2,3,4,5,6,7,8,9]);
const subscribe = source
.bufferCount(5) // Buffer people ids to ensure the size won't exceed 5 for api calls
.flatMap(peopleIds => peopleApi.getPeople(peopleIds))
.map(people => people.map(user => user.policyId))
.flatMap(policyIds => policyApi.getPolicyDetails(policyIds))
.subscribe(policies => console.log(policies));

这是一个模拟你的 api 的 sn-p

const mockPeople = [
  { "id": 1, "name": "Jack", "policyId": "p-01" },
  { "id": 2, "name": "Isobel", "policyId": "p-02" },
  { "id": 3, "name": "Steve", "policyId": "p-03" },
  { "id": 4, "name": "James", "policyId": "p-04" },
  { "id": 5, "name": "Marty", "policyId": "p-05" },
  { "id": 6, "name": "Janis", "policyId": "p-06" },
  { "id": 7, "name": "Annabel", "policyId": "p-07" },
  { "id": 8, "name": "Flora", "policyId": "p-08" },
  { "id": 9, "name": "Richard", "policyId": "p-09" },
]

const mockPolicies = [
  { "id": "p-01", "details": "Details for Jack's policy" },
  { "id": "p-02", "details": "Details for Isobel's policy" },
  { "id": "p-03", "details": "Details for Steve's policy" },
  { "id": "p-04", "details": "Details for James's policy" },
  { "id": "p-05", "details": "Details for Marty's policy" },
  { "id": "p-06", "details": "Details for Janis's policy" },
  { "id": "p-07", "details": "Details for Annabel's policy" },
  { "id": "p-08", "details": "Details for Flora's policy" },
  { "id": "p-09", "details": "Details for Richard's policy" }  
]

let mockGetPeople = async (peopleIds) => {
	let filteredPeople = mockPeople.filter(user => {
  	return peopleIds.indexOf(user.id) !== -1;
  });
	return Promise.resolve(filteredPeople);
}
let mockGetPolicies = async (policyIds) => {
	let filteredPolicies = mockPolicies.filter(policy => {
  	return policyIds.indexOf(policy.id) !== -1;
  });
	return Promise.resolve(filteredPolicies);
}


const source = Rx.Observable.from([1,2,3,4,5,6,7,8,9]);
const subscribe = source
.bufferCount(5)
.flatMap(peopleIds => mockGetPeople(peopleIds)) // mock of peopleApi.getPeople
.map(people => people.map(user => user.policyId))
.flatMap(policyIds => mockGetPolicies(policyIds)) // mock of policyApi.getPolicyDetails
.subscribe(policies => console.log(policies));
&lt;script src="https://npmcdn.com/@reactivex/rxjs@5.0.0-beta.8/dist/global/Rx.umd.js"&gt;&lt;/script&gt;

【讨论】:

    【解决方案2】:

    如果此 API 允许您并行执行请求,您可以采用这种方法:

    const allPolicies = await Promise.all(splitListOfIds.map(async peopleIds => {
      const people = await peopleApi.getPeople(peopleIds)
      const policyIds = people.map(p => p.policyId)
      const policies = await policyApi.getPolicyDetails(policyIds)
      return policies;
    }));
    

    根据 API 在后台执行的操作,如果您有太多的并行操作,这可能会给您带来麻烦。如果是这种情况,那么您需要实现某种最大并行机制,就像这样(未经测试,尽管可能已经有库):

    async function processInParallel(maxParallelism, data, taskFn) {
      const results = [];
      const inFlight = new Set();  
    
      for (let i = 0; i < data.length; i++) {
        while (inFlight.size >= maxParallelism) {
          // Wait for at least one to complete
          await Promise.race([...inFlight]);
        } 
    
        const task = taskFn(data[i]).then(result => {
          results.push(result);
          inFlight.delete(task);
        });
        inFlight.add(task);
      }
    
      await Promise.all([...inFlight]);
    
      return results;
    }
    
    // Usage
    const allPolicies = await processInParallel(10, splitListOfIds, async peopleIds => {
      const people = await peopleApi.getPeople(peopleIds)
      const policyIds = people.map(p => p.policyId)
      const policies = await policyApi.getPolicyDetails(policyIds)
      return policies;
    }));
    

    【讨论】:

    • 看起来很有希望!让我试试看,如果成功了,我会接受你的回答
    【解决方案3】:

    您必须 map 对 Promise 的每个异步调用,然后一起等待它们,如下所示:

    async function callAsync() {
    
        const asyncCalls = splitListOfIds.map(async function(pieceOfListOfIds) {
    
            const people = await peopleApi.getPeople(pieceOfListOfIds);
            const policyIds = people.map(p => p.policyId);
            const policies = await policyApi.getPolicyDetails(policyIds);
    
            allPolicies.push(policies);
    
        });
    
        await Promise.all(asyncCalls);
    }
    

    基本上每个async function 都会返回一个Promise,您将收集到asyncCalls 数组中,然后使用Promise.all 等待每个Promise 解决。
    如果您只使用forEach,您的程序将继续执行而无需等待各种承诺。

    编辑:

    注意:此代码将并行执行所有 API 调用

    【讨论】:

    • 如果我错了,请纠正我,但你的和 Jacobs 之间没有功能差异,对吧?
    • 是的,Jacob 在我写作的时候发帖了。如果此解决方案满足您的需求,请接受 Jacob 的回答,因为他先到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-25
    • 1970-01-01
    • 1970-01-01
    • 2012-07-25
    • 1970-01-01
    • 2018-07-13
    相关资源
    最近更新 更多