[javascript] ES6의 Promise.all ()을 사용할 때 동시성을 제한하는 가장 좋은 방법은 무엇입니까?

데이터베이스에서 쿼리 된 목록을 반복하고 해당 목록의 각 요소에 대해 HTTP 요청을 만드는 코드가 있습니다. 이 목록은 때때로 상당히 많은 수 (수천 개)가 될 수 있으며 수천 개의 동시 HTTP 요청이있는 웹 서버에 연결되지 않도록하고 싶습니다.

이 코드의 축약 된 버전은 현재 다음과 같습니다.

function getCounts() {
  return users.map(user => {
    return new Promise(resolve => {
      remoteServer.getCount(user) // makes an HTTP request
      .then(() => {
        /* snip */
        resolve();
      });
    });
  });
}

Promise.all(getCounts()).then(() => { /* snip */});

이 코드는 Node 4.3.2에서 실행됩니다. 다시 말하면 Promise.all주어진 시간에 특정 수의 약속 만 진행되도록 관리 할 수 있습니까?



답변

참고 Promise.all()자체가하는 약속을 만드는 작업을 시작하는 약속을 트리거하지 않습니다.

이를 염두에두고 한 가지 해결책은 약속이 해결 될 때마다 새 약속을 시작해야하는지 또는 이미 한계에 도달했는지 확인하는 것입니다.

그러나 여기서 바퀴를 재발 명 할 필요는 없습니다. 이 목적으로 사용할 수있는 라이브러리는es6-promise-pool . 그들의 예에서 :

// On the Web, leave out this line and use the script tag above instead. 
var PromisePool = require('es6-promise-pool')

var promiseProducer = function () {
  // Your code goes here. 
  // If there is work left to be done, return the next work item as a promise. 
  // Otherwise, return null to indicate that all promises have been created. 
  // Scroll down for an example. 
}

// The number of promises to process simultaneously. 
var concurrency = 3

// Create a pool. 
var pool = new PromisePool(promiseProducer, concurrency)

// Start the pool. 
var poolPromise = pool.start()

// Wait for the pool to settle. 
poolPromise.then(function () {
  console.log('All promises fulfilled')
}, function (error) {
  console.log('Some promise rejected: ' + error.message)
})


답변

P- 리밋

Promise 동시성 제한을 사용자 지정 스크립트, bluebird, es6-promise-pool 및 p-limit와 비교했습니다. 나는 p-limit 가 이러한 요구에 대해 가장 간단하고 제거 된 구현 이라고 생각 합니다. 설명서를 참조하십시오 .

요구 사항

예에서 비동기와 호환하려면

내 예

이 예제에서는 배열의 모든 URL에 대해 함수를 실행해야합니다 (예 : API 요청). 여기서 이것을라고 fetchData()합니다. 처리 할 수천 개의 항목이있는 경우 동시성은 CPU 및 메모리 리소스를 절약하는 데 확실히 유용 할 것입니다.

const pLimit = require('p-limit');

// Example Concurrency of 3 promise at once
const limit = pLimit(3);

let urls = [
    "http://www.exampleone.com/",
    "http://www.exampletwo.com/",
    "http://www.examplethree.com/",
    "http://www.examplefour.com/",
]

// Create an array of our promises using map (fetchData() returns a promise)
let promises = urls.map(url => {

    // wrap the function we are calling in the limit function we defined above
    return limit(() => fetchData(url));
});

(async () => {
    // Only three promises are run at once (as defined above)
    const result = await Promise.all(promises);
    console.log(result);
})();

콘솔 로그 결과는 해결 된 약속 응답 데이터의 배열입니다.


답변

사용 Array.prototype.splice

while (funcs.length) {
  // 100 at at time
  await Promise.all( funcs.splice(0, 100).map(f => f()) )
}


답변

반복기가 작동하는 방식과 사용 방식을 알고 있다면 별도의 라이브러리가 필요하지 않을 것입니다. 자신 만의 동시성을 구축하는 것이 매우 쉬워 질 수 있기 때문입니다. 시연하겠습니다.

/* [Symbol.iterator]() is equivalent to .values()
const iterator = [1,2,3][Symbol.iterator]() */
const iterator = [1,2,3].values()


// loop over all items with for..of
for (const x of iterator) {
  console.log('x:', x)

  // notices how this loop continues the same iterator
  // and consumes the rest of the iterator, making the
  // outer loop not logging any more x's
  for (const y of iterator) {
    console.log('y:', y)
  }
}

동일한 반복자를 사용하고 작업자간에 공유 할 수 있습니다.

.entries()대신 사용했다면 .values()2D 배열을 얻었을 것입니다.이 배열 [[index, value]]은 2의 동시성으로 아래에서 설명합니다.

const sleep = t => new Promise(rs => setTimeout(rs, t))

async function doWork(iterator) {
  for (let [index, item] of iterator) {
    await sleep(1000)
    console.log(index + ': ' + item)
  }
}

const iterator = Array.from('abcdefghij').entries()
const workers = new Array(2).fill(iterator).map(doWork)
//    ^--- starts two workers sharing the same iterator

Promise.allSettled(workers).then(() => console.log('done'))

이것의 이점은 모든 것을 한 번에 준비하는 대신 생성기 기능을 가질 수 있다는 것입니다.


참고 : 예제 async-pool 과 비교했을 때 이와 다른 점은 두 작업자를 생성한다는 것입니다. 따라서 한 작업자가 인덱스 5에서 어떤 이유로 든 오류를 던지면 다른 작업자가 나머지 작업을 중단하지 않습니다. 그래서 당신은 2 개의 동시성을 1로 내려갑니다. (그래서 여기서 멈추지 않을 것입니다) 그래서 제 조언은 doWork함수 내부의 모든 오류를 잡는 것입니다.


답변

bluebird의 Promise.map 은 동시성 옵션을 사용하여 병렬로 실행해야하는 promise의 수를 제어 할 수 있습니다. 때로는 .allpromise 배열을 만들 필요 가 없기 때문에 더 쉽습니다 .

const Promise = require('bluebird')

function getCounts() {
  return Promise.map(users, user => {
    return new Promise(resolve => {
      remoteServer.getCount(user) // makes an HTTP request
      .then(() => {
        /* snip */
        resolve();
       });
    });
  }, {concurrency: 10}); // <---- at most 10 http requests at a time
}


답변

http 요청을 제한하기 위해 promise를 사용하는 대신 노드의 기본 제공 http.Agent.maxSockets를 사용하십시오 . 이렇게하면 라이브러리를 사용하거나 고유 한 풀링 코드를 작성해야하는 요구 사항이 제거되고 제한하는 항목을 더 잘 제어 할 수있는 추가 이점이 있습니다.

agent.maxSockets

기본적으로 무한대로 설정됩니다. 에이전트가 오리진 당 열 수있는 동시 소켓 수를 결정합니다. Origin은 ‘host : port’또는 ‘host : port : localAddress’조합입니다.

예를 들면 :

var http = require('http');
var agent = new http.Agent({maxSockets: 5}); // 5 concurrent connections per origin
var request = http.request({..., agent: agent}, ...);

동일한 오리진에 여러 요청을하는 경우 keepAlivetrue 로 설정 하는 것도 도움이 될 수 있습니다 (자세한 내용은 위의 문서 참조).


답변

async-pool 라이브러리를 제안합니다 : https://github.com/rxaviers/async-pool

npm install tiny-async-pool

기술:

네이티브 ES6 / ES7을 사용하여 제한된 동시성으로 여러 약속 반환 및 비동기 함수 실행

asyncPool은 제한된 동시성 풀에서 여러 약속 반환 및 비동기 함수를 실행합니다. 약속 중 하나가 거부되는 즉시 거부됩니다. 모든 약속이 완료되면 해결됩니다. 가능한 한 빨리 반복기 함수를 호출합니다 (동시성 제한 아래).

용법:

const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
await asyncPool(2, [1000, 5000, 3000, 2000], timeout);
// Call iterator (i = 1000)
// Call iterator (i = 5000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 1000 finishes
// Call iterator (i = 3000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 3000 finishes
// Call iterator (i = 2000)
// Itaration is complete, wait until running ones complete...
// 5000 finishes
// 2000 finishes
// Resolves, results are passed in given array order `[1000, 5000, 3000, 2000]`.