데이터베이스에서 쿼리 된 목록을 반복하고 해당 목록의 각 요소에 대해 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 가 이러한 요구에 대해 가장 간단하고 제거 된 구현 이라고 생각 합니다. 설명서를 참조하십시오 .
요구 사항
예에서 비동기와 호환하려면
- ECMAScript 2017 (버전 8)
- 노드 버전> 8.2.1
내 예
이 예제에서는 배열의 모든 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의 수를 제어 할 수 있습니다. 때로는 .all
promise 배열을 만들 필요 가 없기 때문에 더 쉽습니다 .
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}, ...);
동일한 오리진에 여러 요청을하는 경우 keepAlive
true 로 설정 하는 것도 도움이 될 수 있습니다 (자세한 내용은 위의 문서 참조).
답변
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]`.