é«åºŠãªJavaScript䞊è¡ã¹ããªãŒã åŠçæè¡ãæ¢æ±ãé«ã¹ã«ãŒãããã®APIåŒã³åºãããã¡ã€ã«åŠçãããŒã¿ãã€ãã©ã€ã³åãã®äžŠåã€ãã¬ãŒã¿ãã«ããŒæ§ç¯æ³ã解説ããŸãã
髿§èœJavaScriptã®éµïŒã€ãã¬ãŒã¿ãã«ããŒã®äžŠååŠçãšäžŠè¡ã¹ããªãŒã ã®åŸ¹åºè§£èª¬
çŸä»£ã®ãœãããŠã§ã¢éçºã®äžçã§ã¯ãããŒã¿ãçæ§ã§ããç§ãã¡ã¯åžžã«ãAPIãããŒã¿ããŒã¹ããã¡ã€ã«ã·ã¹ãã ãªã©ããåŸãããèšå€§ãªããŒã¿ã®ã¹ããªãŒã ãåŠçãããšãã課é¡ã«çŽé¢ããŠããŸããJavaScriptéçºè ã«ãšã£ãŠãèšèªã®ã·ã³ã°ã«ã¹ã¬ãããšããæ§è³ªã¯é倧ãªããã«ããã¯ãšãªãåŸãŸããå€§èŠæš¡ãªããŒã¿ã»ãããåŠçããé·æéå®è¡ãããåæã«ãŒãã¯ããã©ãŠã¶ã®UIãããªãŒãºãããããNode.jsã®ãµãŒããŒã忢ããããããå¯èœæ§ããããŸããã§ã¯ãã©ãããã°ãããã®éäžçãªã¯ãŒã¯ããŒããå¹ççã«åŠçã§ãããå¿çæ§ã®é«ã髿§èœãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããã®ã§ããããïŒ
ãã®çãã¯ãéåæãã¿ãŒã³ãç¿åŸããäžŠè¡æ§ãåãå ¥ããããšã«ãããŸããJavaScriptã«ä»åŸå°å ¥ãããã€ãã¬ãŒã¿ãã«ããŒã®ææ¡ã¯ãåæçãªã³ã¬ã¯ã·ã§ã³ã®æ±ãæ¹ãé©åœçã«å€ããããšãæåŸ ãããŠããŸããããã®çã®åã¯ããã®ååãéåæã®äžçã«æ¡åŒµãããšãã«è§£ãæŸãããŸãããã®èšäºã§ã¯ãã€ãã¬ãŒã¿é¢šã¹ããªãŒã ã®äžŠååŠçãšããæŠå¿µãæ·±ãæãäžããŸããé«ã¹ã«ãŒãããã®APIåŒã³åºãã䞊åããŒã¿å€æãªã©ã®ã¿ã¹ã¯ãå®è¡ããããã®ç¬èªã®äžŠè¡ã¹ããªãŒã æŒç®åãæ§ç¯ããæ¹æ³ãæ¢æ±ããããã©ãŒãã³ã¹ã®ããã«ããã¯ãå¹ççã§ãã³ããããã³ã°ãªãã€ãã©ã€ã³ã«å€ããŠãããŸãã
åºç€ïŒã€ãã¬ãŒã¿ãšã€ãã¬ãŒã¿ãã«ããŒã®çè§£
èµ°ãåã«ããŸãæ©ãæ¹ãåŠã°ãªããã°ãªããŸãããç§ãã¡ã®é«åºŠãªãã¿ãŒã³ã®åå°ãšãªããJavaScriptã«ãããã€ãã¬ãŒã·ã§ã³ã®æ žãšãªãæŠå¿µãç°¡åã«åŸ©ç¿ããŸãããã
ã€ãã¬ãŒã¿ãããã³ã«ãšã¯ïŒ
ã€ãã¬ãŒã¿ãããã³ã«ã¯ãå€ã®ã·ãŒã±ã³ã¹ãçæããããã®æšæºçãªæ¹æ³ã§ãããªããžã§ã¯ããã€ãã¬ãŒã¿ã§ãããšã¯ã2ã€ã®ããããã£ãæã€ãªããžã§ã¯ããè¿ãnext()ã¡ãœãããæã£ãŠããå Žåã§ãïŒ
value: ã·ãŒã±ã³ã¹ã®æ¬¡ã®å€ãdone: ã€ãã¬ãŒã¿ãçµç«¯ã«éããå Žåã¯trueãããã§ãªããã°falseãšãªãããŒã«å€ã
ç¹å®ã®æ°ãŸã§ã«ãŠã³ãã¢ããããã«ã¹ã¿ã ã€ãã¬ãŒã¿ã®ç°¡åãªäŸã次ã«ç€ºããŸãïŒ
function createCounter(limit) {
let count = 0;
return {
next: function() {
if (count < limit) {
return { value: count++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
const counter = createCounter(3);
console.log(counter.next()); // { value: 0, done: false }
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.next()); // { value: undefined, done: true }
é
åãããããæååã®ãããªãªããžã§ã¯ãã¯ãã€ãã¬ãŒã¿ãè¿ã[Symbol.iterator]ã¡ãœãããæã£ãŠããããããiterableïŒå埩å¯èœïŒãã§ããããã«ãããfor...ofã«ãŒãã§ãããã䜿çšããããšãã§ããŸãã
ã€ãã¬ãŒã¿ãã«ããŒããããããã®
TC39 ã€ãã¬ãŒã¿ãã«ããŒææ¡ã¯ãIterator.prototypeã«çŽæ¥ãŠãŒãã£ãªãã£ã¡ãœãã矀ã远å ããããšãç®çãšããŠããŸããããã¯ãç§ãã¡ãæ¢ã«Array.prototypeã§æã£ãŠããmapãfilterãreduceã®ãããªåŒ·åãªã¡ãœããã«äŒŒãŠããŸãããããããiterableãªããžã§ã¯ãã«å¯ŸããŠæ©èœããŸããããã«ããããã宣èšçã§ã¡ã¢ãªå¹çã®è¯ãæ¹æ³ã§ã·ãŒã±ã³ã¹ãåŠçã§ããŸãã
ã€ãã¬ãŒã¿ãã«ããŒå°å ¥åïŒåŸæ¥ã®æ¹æ³ïŒïŒ
const numbers = [1, 2, 3, 4, 5, 6];
// To get the sum of squares of even numbers, we create intermediate arrays.
const evenNumbers = numbers.filter(n => n % 2 === 0);
const squares = evenNumbers.map(n => n * n);
const sum = squares.reduce((acc, n) => acc + n, 0);
console.log(sum); // 56 (2*2 + 4*4 + 6*6)
ã€ãã¬ãŒã¿ãã«ããŒå°å ¥åŸïŒææ¡ãããŠããæªæ¥ïŒïŒ
const numbersIterator = [1, 2, 3, 4, 5, 6].values();
// No intermediate arrays are created. Operations are lazy and pulled one by one.
const sum = numbersIterator
.filter(n => n % 2 === 0) // returns a new iterator
.map(n => n * n) // returns another new iterator
.reduce((acc, n) => acc + n, 0); // consumes the final iterator
console.log(sum); // 56
éèŠãªç¹ã¯ããããã®ææ¡ããããã«ããŒã¯é次çãã€åæçã«åäœãããšããããšã§ããäžã€ã®ã¢ã€ãã ãååŸããããããã§ãŒã³ãéããŠåŠçããæ¬¡ã«æ¬¡ã®ã¢ã€ãã ãååŸããŸããããã¯ã¡ã¢ãªå¹çã«ã¯åªããŠããŸãããæéã®ãããI/OããŠã³ããªæäœã«é¢ããããã©ãŒãã³ã¹ã®åé¡ã¯è§£æ±ºããŸããã
ã·ã³ã°ã«ã¹ã¬ããJavaScriptã«ããã䞊è¡åŠçã®èª²é¡
JavaScriptã®å®è¡ã¢ãã«ã¯ãã€ãã³ãã«ãŒããäžå¿ã«å±éããã·ã³ã°ã«ã¹ã¬ããã§ããããšã§æåã§ããããã¯ãã¡ã€ã³ã®ã³ãŒã«ã¹ã¿ãã¯äžã§äžåºŠã«äžã€ã®ã³ãŒãããå®è¡ã§ããªãããšãæå³ããŸããåæçãªCPUéçŽåã®ã¿ã¹ã¯ïŒå·šå€§ãªã«ãŒããªã©ïŒãå®è¡ãããŠããéãããã¯ã³ãŒã«ã¹ã¿ãã¯ããããã¯ããŸãããã©ãŠã¶ã§ã¯ãããã¯UIã®ããªãŒãºã«ã€ãªãããŸãããµãŒããŒã§ã¯ãä»ã®åä¿¡ãªã¯ãšã¹ãã«å¿çã§ããªããªãããšãæå³ããŸãã
ããã§ãäžŠè¡æ§ïŒconcurrencyïŒãšäžŠåæ§ïŒparallelismïŒãåºå¥ããå¿ èŠããããŸãïŒ
- äžŠè¡æ§ãšã¯ãäžå®æéã«è€æ°ã®ã¿ã¹ã¯ã管çããããšã§ããã€ãã³ãã«ãŒãã«ãããJavaScriptã¯é«ãäžŠè¡æ§ãå®çŸã§ããŸãããããã¯ãŒã¯ãªã¯ãšã¹ãïŒI/OæäœïŒãéå§ãããã®å¿çãåŸ ã£ãŠããéã«ããŠãŒã¶ãŒã®ã¯ãªãã¯ãä»ã®ã€ãã³ããåŠçã§ããŸããã¿ã¹ã¯ã¯åæã«å®è¡ãããã®ã§ã¯ãªãã亀äºã«åŠçãããŸãã
- äžŠåæ§ãšã¯ãè€æ°ã®ã¿ã¹ã¯ãå šãåæã«å®è¡ããããšã§ããJavaScriptã«ãããçã®äžŠåæ§ã¯ãéåžžããã©ãŠã¶ã®Web WorkersãNode.jsã®Worker Threads/Child Processesã®ãããªæè¡ã䜿çšããŠéæãããŸãããããã¯ç¬èªã®ã€ãã³ãã«ãŒããæã€å¥ã®ã¹ã¬ãããæäŸããŸãã
ãã®èšäºã®ç®çã®ããã«ãç§ãã¡ã¯I/OããŠã³ããªæäœïŒAPIåŒã³åºããªã©ïŒã«å¯ŸããŠé«ãäžŠè¡æ§ãéæããããšã«çŠç¹ãåœãŠãŸããããã¯ãçŸå®äžçã§æã倧ããªããã©ãŒãã³ã¹åäžãèŠèŸŒããåéã ããã§ãã
ãã©ãã€ã ã·ããïŒéåæã€ãã¬ãŒã¿
æéãšãšãã«å°çããããŒã¿ã¹ããªãŒã ïŒãããã¯ãŒã¯ãªã¯ãšã¹ãã倧ããªãã¡ã€ã«ããã®ãã®ãªã©ïŒãæ±ãããã«ãJavaScriptã¯éåæã€ãã¬ãŒã¿ãããã³ã«ãå°å
¥ããŸãããããã¯åæçãªãã®ãšéåžžã«äŒŒãŠããŸãããéèŠãªéãããããŸãïŒnext()ã¡ãœããã{ value, done }ãªããžã§ã¯ãã«è§£æ±ºãããPromiseãè¿ãç¹ã§ãã
ããã«ãããå
šãŠã®ããŒã¿ãäžåºŠã«å©çšå¯èœã§ã¯ãªãããŒã¿ãœãŒã¹ãæ±ãããšãã§ããŸãããããã®éåæã¹ããªãŒã ãåªé
ã«æ¶è²»ããããã«ãç§ãã¡ã¯for await...ofã«ãŒãã䜿çšããŸãã
APIããããŒã¿ã®ããŒãžããã§ããããã·ãã¥ã¬ãŒã·ã§ã³ãè¡ãéåæã€ãã¬ãŒã¿ãäœæããŠã¿ãŸãããïŒ
async function* fetchPaginatedData(url) {
let nextPageUrl = url;
while (nextPageUrl) {
console.log(`Fetching from ${nextPageUrl}...`);
const response = await fetch(nextPageUrl);
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const data = await response.json();
// Yield each item from the current page's results
for (const item of data.results) {
yield item;
}
// Move to the next page, or stop if there isn't one
nextPageUrl = data.nextPage;
}
}
// Usage:
async function processUsers() {
const userStream = fetchPaginatedData('https://api.example.com/users');
for await (const user of userStream) {
console.log(`Processing user: ${user.name}`);
// This is still sequential processing. We wait for one user to be logged
// before the next one is even requested from the stream.
}
}
ããã¯åŒ·åãªãã¿ãŒã³ã§ãããã«ãŒãå ã®ã³ã¡ã³ãã«æ³šæããŠãã ãããåŠçã¯é次çã§ãããã`process user`ãå¥ã®é ãéåææäœïŒããŒã¿ããŒã¹ãžã®ä¿åãªã©ïŒã䌎ãå Žåãæ¬¡ã®åŠçãéå§ããåã«åæäœãå®äºããã®ãåŸ ã€ããšã«ãªããŸãããããããç§ãã¡ãæé€ãããããã«ããã¯ã§ãã
ã€ãã¬ãŒã¿ãã«ããŒãçšãã䞊è¡ã¹ããªãŒã æäœã®èšèš
ããŠãããããæ¬é¡ã§ããåã®ã¢ã€ãã ã®çµäºãåŸ
ããã«ãéåæã¹ããªãŒã ã®ã¢ã€ãã ã䞊è¡ããŠåŠçããã«ã¯ã©ãããã°ããã®ã§ããããïŒç§ãã¡ã¯ãã«ã¹ã¿ã éåæã€ãã¬ãŒã¿ãã«ããŒãæ§ç¯ããŸãããããasyncMapConcurrentãšåŒã¶ããšã«ããŸãããã
ãã®é¢æ°ã¯3ã€ã®åŒæ°ãåããŸãïŒ
sourceIterator: ã¢ã€ãã ãååŸãããéåæã€ãã¬ãŒã¿ãmapperFn: åã¢ã€ãã ã«é©çšãããéåæé¢æ°ãconcurrency: åæã«å®è¡ã§ããmapperFnæäœã®æ°ãå®çŸ©ããæ°å€ã
äžå¿çãªæŠå¿µïŒPromiseã®ã¯ãŒã«ãŒããŒã«
æŠç¥ã¯ãã¢ã¯ãã£ããªPromiseã®ãããŒã«ããŸãã¯ã»ãããç¶æããããšã§ãããã®ããŒã«ã®ãµã€ãºã¯ãconcurrencyãã©ã¡ãŒã¿ã«ãã£ãŠå¶éãããŸãã
- ãœãŒã¹ã€ãã¬ãŒã¿ããã¢ã€ãã ãååŸãããããã«å¯ŸããŠéåæã®
mapperFnãéå§ããŸãã mapperFnããè¿ãããPromiseãã¢ã¯ãã£ããªããŒã«ã«è¿œå ããŸãã- ããŒã«ããã£ã±ãã«ãªããŸã§ïŒãµã€ãºã
concurrencyã¬ãã«ã«çãããªããŸã§ïŒãããç¶ããŸãã - ããŒã«ããã£ã±ãã«ãªã£ããã*ãã¹ãŠ*ã®PromiseãåŸ
ã€ã®ã§ã¯ãªãã
Promise.race()ã䜿çšããŠ*ãããã1ã€*ãå®äºããã®ãåŸ ã¡ãŸãã - Promiseãå®äºãããšããã®çµæãyieldããããŒã«ããåé€ããŸããããã§æ°ããPromiseã远å ããã¹ããŒã¹ãã§ããŸãã
- ãœãŒã¹ããæ¬¡ã®ã¢ã€ãã ãååŸãããã®åŠçãéå§ããæ°ããPromiseãããŒã«ã«è¿œå ããŠããã®ãµã€ã¯ã«ãç¹°ãè¿ããŸãã
ããã«ãããåŠçãã¹ãããŒã¿ãããéããå®çŸ©ãããäžŠè¡æ§ã®äžéãŸã§åžžã«äœæ¥ãè¡ãããé£ç¶çãªãããŒãçãŸããŸããããã«ãããåŠçãã€ãã©ã€ã³ãã¢ã€ãã«ç¶æ ã«ãªãããšã¯ãããŸããã
asyncMapConcurrentã®æ®µéçå®è£
ãã®ãŠãŒãã£ãªãã£ãæ§ç¯ããŸããããããã¯éåæãžã§ãã¬ãŒã¿é¢æ°ãšãªããéåæã€ãã¬ãŒã¿ãããã³ã«ãç°¡åã«å®è£ ã§ããŸãã
async function* asyncMapConcurrent(sourceIterator, mapperFn, concurrency = 5) {
const activePromises = new Set();
const source = sourceIterator[Symbol.asyncIterator]();
while (true) {
// 1. Fill the pool up to the concurrency limit
while (activePromises.size < concurrency) {
const { value, done } = await source.next();
if (done) {
// Source iterator is exhausted, break the inner loop
break;
}
const promise = (async () => {
try {
return { result: await mapperFn(value), error: null };
} catch (e) {
return { result: null, error: e };
}
})();
activePromises.add(promise);
// Also, attach a cleanup function to the promise to remove it from the set upon completion.
promise.finally(() => activePromises.delete(promise));
}
// 2. Check if we are done
if (activePromises.size === 0) {
// The source is exhausted and all active promises have finished.
return; // End the generator
}
// 3. Wait for any promise in the pool to finish
const completed = await Promise.race(activePromises);
// 4. Handle the result
if (completed.error) {
// We can decide on an error handling strategy. Here, we re-throw.
throw completed.error;
}
// 5. Yield the successful result
yield completed.result;
}
}
å®è£ ã詳ããèŠãŠãããŸãããïŒ
activePromisesã«ã¯Setã䜿çšããŸããSetã¯ãŠããŒã¯ãªãªããžã§ã¯ãïŒPromiseãªã©ïŒãä¿åããã®ã«äŸ¿å©ã§ã远å ãåé€ãé«éã§ãã- å€åŽã®
while (true)ã«ãŒãã¯ãæç€ºçã«çµäºãããŸã§ããã»ã¹ãç¶ç¶ãããŸãã - å
åŽã®
while (activePromises.size < concurrency)ã«ãŒãã¯ãã¯ãŒã«ãŒããŒã«ãåãã圹å²ãæ ããŸããããã¯sourceã€ãã¬ãŒã¿ããç¶ç¶çã«ã¢ã€ãã ãååŸããŸãã - ãœãŒã¹ã€ãã¬ãŒã¿ã
doneã«ãªã£ãããæ°ããPromiseã®è¿œå ã忢ããŸãã - åæ°ããã¢ã€ãã ã«å¯ŸããŠãå³åº§ã«éåæIIFEïŒå³æå®è¡é¢æ°åŒïŒãåŒã³åºããŸããããã«ããã
mapperFnã®å®è¡ãããã«éå§ãããŸããããããŒããã®æœåšçãªãšã©ãŒãé©åã«åŠçããäžè²«ãããªããžã§ã¯ã圢ç¶{ result, error }ãè¿ãããã«ãããã`try...catch`ãããã¯ã§ã©ããããŸãã - éèŠãªã®ã¯ã
promise.finally(() => activePromises.delete(promise))ã䜿çšããããšã§ããããã«ãããPromiseã解決ããããæåŠããããã«é¢ããããã¢ã¯ãã£ãã»ããããåé€ãããããšãä¿èšŒãããæ°ããäœæ¥ã®ããã®ã¹ããŒã¹ã確ä¿ãããŸããããã¯ãPromise.raceã®åŸã«æåã§PromiseãèŠã€ããŠåé€ããããšãããããã¯ãªãŒã³ãªã¢ãããŒãã§ãã Promise.race(activePromises)ã¯äžŠè¡åŠçã®å¿èéšã§ããããã¯ãã»ããå ã®*æå*ã®Promiseã解決ãŸãã¯æåŠããããšããã«è§£æ±ºãŸãã¯æåŠãããæ°ããPromiseãè¿ããŸãã- Promiseãå®äºãããšãã©ãããããçµæãæ€æ»ããŸãããšã©ãŒãããã°ããããã¹ããŒããŠãžã§ãã¬ãŒã¿ãçµäºãããŸãïŒãã§ã€ã«ãã¡ã¹ãæŠç¥ïŒãæåããã°ã
asyncMapConcurrentãžã§ãã¬ãŒã¿ã®ã³ã³ã·ã¥ãŒãã«çµæãyieldããŸãã - æçµçãªçµäºæ¡ä»¶ã¯ããœãŒã¹ãæ¯æžãã
activePromisesã»ããã空ã«ãªã£ããšãã§ãããã®æç¹ã§ãå€åŽã®ã«ãŒãã®æ¡ä»¶activePromises.size === 0ãæºããããreturnããŸããããã¯éåæãžã§ãã¬ãŒã¿ã®çµäºãç¥ãããŸãã
å®è·µçãªãŠãŒã¹ã±ãŒã¹ãšã°ããŒãã«ãªäŸ
ãã®ãã¿ãŒã³ã¯åãªãåŠè¡çãªæŒç¿ã§ã¯ãããŸãããçŸå®äžçã®ã¢ããªã±ãŒã·ã§ã³ã«å€§ããªåœ±é¿ãäžããŸããããã€ãã®ã·ããªãªãæ¢ã£ãŠã¿ãŸãããã
ãŠãŒã¹ã±ãŒã¹1ïŒé«ã¹ã«ãŒãããã®APIã€ã³ã¿ã©ã¯ã·ã§ã³
ã·ããªãªïŒã°ããŒãã«ãªeã³ããŒã¹ãã©ãããã©ãŒã åãã®ãµãŒãã¹ãæ§ç¯ããŠãããšæ³åããŠãã ããã50,000åã®ååIDã®ãªã¹ãããããããããã«ã€ããŠãç¹å®å°åã®ææ°äŸ¡æ ŒãååŸããããã«äŸ¡æ ŒAPIãåŒã³åºãå¿ èŠããããŸãã
鿬¡åŠçã®ããã«ããã¯ïŒ
async function updateAllPrices(productIds) {
const startTime = Date.now();
for (const id of productIds) {
await fetchPrice(id); // Assume this takes ~200ms
}
console.log(`Total time: ${(Date.now() - startTime) / 1000}s`);
}
// Estimated time for 50,000 products: 50,000 * 0.2s = 10,000 seconds (~2.7 hours!)
䞊è¡åŠçã«ãã解決çïŒ
// Helper function to simulate a network request
function fetchPrice(productId) {
return new Promise(resolve => {
setTimeout(() => {
const price = (Math.random() * 100).toFixed(2);
console.log(`Fetched price for ${productId}: $${price}`);
resolve({ productId, price });
}, 200 + Math.random() * 100); // Simulate variable network latency
});
}
async function updateAllPricesConcurrently() {
const productIds = Array.from({ length: 50 }, (_, i) => `product-${i + 1}`);
const idIterator = productIds.values(); // Create a simple iterator
// Use our concurrent mapper with a concurrency of 10
const priceStream = asyncMapConcurrent(idIterator, fetchPrice, 10);
const startTime = Date.now();
for await (const priceData of priceStream) {
// Here you would save the priceData to your database
// console.log(`Processed: ${priceData.productId}`);
}
console.log(`Concurrent total time: ${(Date.now() - startTime) / 1000}s`);
}
updateAllPricesConcurrently();
// Expected output: A flurry of "Fetched price..." logs, and a total time
// that is roughly (Total Items / Concurrency) * Avg Time per Item.
// For 50 items at 200ms with concurrency 10: (50/10) * 0.2s = ~1 second (plus latency variance)
// For 50,000 items: (50000/10) * 0.2s = 1000 seconds (~16.7 minutes). A huge improvement!
ã°ããŒãã«ãªèæ ®äºé ïŒAPIã®ã¬ãŒãå¶éã«æ³šæããŠãã ããã䞊è¡ã¬ãã«ãé«ãèšå®ãããããšãIPã¢ãã¬ã¹ããããã¯ãããå¯èœæ§ããããŸããå€ãã®å ¬éAPIã§ã¯ã5ã10ã®äžŠè¡ã¬ãã«ãå®å šãªåºçºç¹ãšãªãããšãå€ãã§ãã
ãŠãŒã¹ã±ãŒã¹2ïŒNode.jsã§ã®äžŠåãã¡ã€ã«åŠç
ã·ããªãªïŒäžæ¬ç»åã¢ããããŒããåãä»ããã³ã³ãã³ã管çã·ã¹ãã ïŒCMSïŒãæ§ç¯ããŠããŸããã¢ããããŒããããåç»åã«ã€ããŠã3ã€ã®ç°ãªããµã€ãºã®ãµã ãã€ã«ãçæããAWS S3ãGoogle Cloud Storageã®ãããªã¯ã©ãŠãã¹ãã¬ãŒãžãããã€ãã«ã¢ããããŒãããå¿ èŠããããŸãã
鿬¡åŠçã®ããã«ããã¯ïŒ1ã€ã®ç»åãå®å šã«åŠçïŒèªã¿èŸŒã¿ã3åãªãµã€ãºã3åã¢ããããŒãïŒããŠããæ¬¡ã®ç»åãéå§ããã®ã¯éåžžã«éå¹çã§ããCPUïŒã¢ããããŒãã®I/OåŸ æ©äžïŒãšãããã¯ãŒã¯ïŒCPUããŠã³ããªãªãµã€ãºäžïŒã®äž¡æ¹ãååã«æŽ»çšã§ããŠããŸããã
䞊è¡åŠçã«ãã解決çïŒ
const fs = require('fs/promises');
const path = require('path');
// Assume 'sharp' for resizing and 'aws-sdk' for uploading are available
async function processImage(filePath) {
console.log(`Processing ${path.basename(filePath)}...`);
const imageBuffer = await fs.readFile(filePath);
const sizes = [{w: 100, h: 100}, {w: 300, h: 300}, {w: 600, h: 600}];
const uploadTasks = sizes.map(async (size) => {
const thumbnailBuffer = await sharp(imageBuffer).resize(size.w, size.h).toBuffer();
return uploadToCloud(thumbnailBuffer, `thumb_${size.w}_${path.basename(filePath)}`);
});
await Promise.all(uploadTasks);
console.log(`Finished ${path.basename(filePath)}`);
return { source: filePath, status: 'processed' };
}
async function run() {
const imageDir = './uploads';
const files = await fs.readdir(imageDir);
const filePaths = files.map(f => path.join(imageDir, f));
// Get the number of CPU cores to set a sensible concurrency level
const concurrency = require('os').cpus().length;
const processingStream = asyncMapConcurrent(filePaths.values(), processImage, concurrency);
for await (const result of processingStream) {
console.log(result);
}
}
ãã®äŸã§ã¯ã䞊è¡ã¬ãã«ãå©çšå¯èœãªCPUã³ã¢ã®æ°ã«èšå®ããŠããŸããããã¯CPUããŠã³ããªã¿ã¹ã¯ã«å¯Ÿããäžè¬çãªãã¥ãŒãªã¹ãã£ãã¯ã§ãããã·ã¹ãã ã䞊åã§åŠçã§ãã以äžã®äœæ¥ã§é飜åç¶æ ã«ãªããªãããã«ããŸãã
ããã©ãŒãã³ã¹ã«é¢ããèæ ®äºé ãšãã¹ããã©ã¯ãã£ã¹
䞊è¡åŠçã®å®è£ ã¯åŒ·åã§ãããäžèœè¬ã§ã¯ãããŸãããè€éãã䌎ããæ éãªæ€èšãå¿ èŠã§ãã
é©åãªäžŠè¡ã¬ãã«ã®éžæ
æé©ãªäžŠè¡ã¬ãã«ã¯ãå¿ ããããå¯èœãªéãé«ããã§ã¯ãããŸãããããã¯ã¿ã¹ã¯ã®æ§è³ªã«äŸåããŸãïŒ
- I/OããŠã³ããªã¿ã¹ã¯ïŒäŸïŒAPIåŒã³åºããããŒã¿ããŒã¹ã¯ãšãªïŒïŒã³ãŒãã¯ã»ãšãã©ã®æéãå€éšãªãœãŒã¹ã®åŸ æ©ã«è²»ãããŸããå€ãã®å Žåãããé«ã䞊è¡ã¬ãã«ïŒäŸïŒ10ã50ããããã¯100ïŒã䜿çšã§ããäž»ã«å€éšãµãŒãã¹ã®ã¬ãŒãå¶éãšèªèº«ã®ãããã¯ãŒã¯åž¯åå¹ ã«ãã£ãŠå¶éãããŸãã
- CPUããŠã³ããªã¿ã¹ã¯ïŒäŸïŒç»ååŠçãè€éãªèšç®ãæå·åïŒïŒã³ãŒãã¯ãã·ã³ã®åŠçèœåã«ãã£ãŠå¶éãããŸããè¯ãåºçºç¹ã¯ã䞊è¡ã¬ãã«ãå©çšå¯èœãªCPUã³ã¢ã®æ°ã«èšå®ããããšã§ãïŒãã©ãŠã¶ã§ã¯
navigator.hardwareConcurrencyãNode.jsã§ã¯os.cpus().lengthïŒããããå€§å¹ ã«é«ãèšå®ãããšãéå°ãªã³ã³ããã¹ãã¹ã€ããã³ã°ãçºçããå®éã«ã¯ããã©ãŒãã³ã¹ãäœäžããå¯èœæ§ããããŸãã
䞊è¡ã¹ããªãŒã ã«ããããšã©ãŒãã³ããªã³ã°
çŸåšã®å®è£
ã¯ããã§ã€ã«ãã¡ã¹ããæŠç¥ããšã£ãŠããŸããããããã®mapperFnããšã©ãŒãã¹ããŒãããšãã¹ããªãŒã å
šäœãçµäºããŸããããã¯æãŸããå ŽåããããŸãããä»ã®ã¢ã€ãã ã®åŠçãç¶ãããå Žåãå€ãã§ãããã«ããŒã倿ŽããŠã倱æãåéããŠå¥ã
ã«yieldããããåã«ãã°ã«èšé²ããŠç¶è¡ãããããããšãã§ããŸãã
ããå ç¢ãªããŒãžã§ã³ã¯æ¬¡ã®ããã«ãªããããããŸããïŒ
// Modified part of the generator
const completed = await Promise.race(activePromises);
if (completed.error) {
console.error("An error occurred in a concurrent task:", completed.error);
// We don't throw, we just continue the loop to wait for the next promise.
// We could also yield the error for the consumer to handle.
// yield { error: completed.error };
} else {
yield completed.result;
}
ããã¯ãã¬ãã·ã£ãŒïŒèå§ïŒç®¡ç
ããã¯ãã¬ãã·ã£ãŒã¯ã¹ããªãŒã åŠçã«ãããŠéèŠãªæŠå¿µã§ããããã¯ãé«éãªããŒã¿ãœãŒã¹ãäœéãªã³ã³ã·ã¥ãŒããå§åãããšãã«çºçããŸããç§ãã¡ã®ãã«ããŒã¹ã®ã€ãã¬ãŒã¿ã¢ãããŒãã®çŸããã¯ãããã¯ãã¬ãã·ã£ãŒãèªåçã«åŠçããç¹ã«ãããŸããç§ãã¡ã®asyncMapConcurrent颿°ã¯ãactivePromisesããŒã«ã«ç©ºãã¹ããããããå Žåã«ã®ã¿sourceIteratorããæ°ããã¢ã€ãã ããã«ããŸããããã¹ããªãŒã ã®ã³ã³ã·ã¥ãŒããyieldãããçµæãåŠçããã®ãé
ãå Žåãç§ãã¡ã®ãžã§ãã¬ãŒã¿ã¯äžæåæ¢ãããã®çµæãšããŠãœãŒã¹ããã®ãã«ã忢ããŸããããã«ãããæªåŠçã®ã¢ã€ãã ã倧éã«ãããã¡ãªã³ã°ãããŠã¡ã¢ãªãæ¯æžããã®ãé²ããŸãã
çµæã®é åº
䞊è¡åŠçã®éèŠãªçµæãšããŠãçµæã¯ãœãŒã¹ããŒã¿ã®å ã®é åºã§ã¯ãªããå®äºããé åºã§yieldãããŸãããœãŒã¹ãªã¹ãã®3çªç®ã®ã¢ã€ãã ã®åŠçãéåžžã«éããæåã®ã¢ã€ãã ãéåžžã«é ãå Žåã3çªç®ã®ã¢ã€ãã ã®çµæãå ã«åãåãããšã«ãªããŸããå ã®é åºãç¶æããããšãèŠä»¶ã§ããå Žåã¯ãçµæã®ãããã¡ãªã³ã°ãšåãœãŒããå«ããããè€éãªãœãªã¥ãŒã·ã§ã³ãæ§ç¯ããå¿ èŠããããããã¯å€§å¹ ãªã¡ã¢ãªãªãŒããŒãããã远å ããŸãã
æªæ¥ïŒãã€ãã£ãå®è£ ãšãšã³ã·ã¹ãã
ç¬èªã®äžŠè¡ãã«ããŒãæ§ç¯ããããšã¯çŽ æŽãããåŠç¿äœéšã§ãããJavaScriptãšã³ã·ã¹ãã ã«ã¯ããããã®ã¿ã¹ã¯ã®ããã®å ç¢ã§å®æŠã§ãã¹ããããã©ã€ãã©ãªãååšããŸãã
- p-mapïŒç§ãã¡ã®
asyncMapConcurrentãšãŸã£ããåãããšãè¡ãã人æ°ããã軜éãªã©ã€ãã©ãªã§ãããããå€ãã®æ©èœãšæé©åãæœãããŠããŸãã - RxJSïŒãªãã¶ãŒããã«ïŒè¶
匷åãªã¹ããªãŒã ã®ãããªãã®ïŒãçšãããªã¢ã¯ãã£ãããã°ã©ãã³ã°ã®ããã®åŒ·åãªã©ã€ãã©ãªã䞊è¡å®è¡çšã«èšå®ã§ãã
mergeMapã®ãããªãªãã¬ãŒã¿ããããŸãã - Node.js Streams APIïŒãµãŒããŒãµã€ãã¢ããªã±ãŒã·ã§ã³åãã«ãNode.jsã¹ããªãŒã ã¯åŒ·åãªããã¯ãã¬ãã·ã£ãŒå¯Ÿå¿ã®ãã€ãã©ã€ã³ãæäŸããŸããããã®APIã¯ç¿åŸãããè€éã«ãªãå ŽåããããŸãã
JavaScriptèšèªãé²åããã«ã€ããŠããã€ããã€ãã£ãã®Iterator.prototype.mapConcurrentãåæ§ã®ãŠãŒãã£ãªãã£ãç»å Žããå¯èœæ§ããããŸããTC39å§å¡äŒã§ã®è°è«ã¯ãéçºè
ã«ããŒã¿ã¹ããªãŒã ãæ±ãããã®ãã匷åã§äººéå·¥åŠã«åºã¥ããããŒã«ãæäŸãããšããæç¢ºãªåŸåã瀺ããŠããŸãããã®èšäºã§åŠãã ããã«ãæ ¹åºã«ããååãçè§£ããããšã§ããããã®ããŒã«ãç»å Žãããšãã«å¹æçã«æŽ»çšã§ããããã«ãªããŸãã
çµè«
ç§ãã¡ã¯JavaScriptã€ãã¬ãŒã¿ã®åºæ¬ããã䞊è¡ã¹ããªãŒã åŠçãŠãŒãã£ãªãã£ã®è€éãªã¢ãŒããã¯ãã£ãŸã§æ ãããŠããŸããããã®éã®ãã¯ãçŸä»£ã®JavaScriptéçºã«é¢ãã匷åãªçå®ãæããã«ããŸãïŒããã©ãŒãã³ã¹ãšã¯ãåäžã®é¢æ°ãæé©åããããšã ãã§ã¯ãªããå¹ççãªããŒã¿ãããŒãèšèšããããšãªã®ã§ãã
éèŠãªãã€ã³ãïŒ
- æšæºã®ã€ãã¬ãŒã¿ãã«ããŒã¯åæçãã€é次çã§ãã
- éåæã€ãã¬ãŒã¿ãš
for await...ofã¯ãããŒã¿ã¹ããªãŒã ãåŠçããããã®ã¯ãªãŒã³ãªæ§æãæäŸããŸãããããã©ã«ãã§ã¯é次çã§ãã - I/OããŠã³ããªã¿ã¹ã¯ã®çã®ããã©ãŒãã³ã¹åäžã¯ã䞊è¡åŠçãã€ãŸãè€æ°ã®ã¢ã€ãã ãäžåºŠã«åŠçããããšããåŸãããŸãã
Promise.raceã§ç®¡çãããPromiseã®ãã¯ãŒã«ãŒããŒã«ãã¯ã䞊è¡ããããŒãæ§ç¯ããããã®å¹æçãªãã¿ãŒã³ã§ãã- ãã®ãã¿ãŒã³ã¯ãå åšçãªããã¯ãã¬ãã·ã£ãŒç®¡çãæäŸããã¡ã¢ãªã®éè² è·ãé²ããŸãã
- 䞊ååŠçãå®è£ ããéã¯ãåžžã«äžŠè¡æ§ã®äžéããšã©ãŒãã³ããªã³ã°ãçµæã®é åºã«æ³šæããŠãã ããã
åçŽãªã«ãŒããè¶ ããŠããããã®é«åºŠãªäžŠè¡ã¹ããªãŒãã³ã°ãã¿ãŒã³ãåãå ¥ããããšã§ãããã©ãŒãã³ã¹ãšã¹ã±ãŒã©ããªãã£ãåäžããã ãã§ãªãã倧éã®ããŒã¿åŠçã®èª²é¡ã«å¯ŸããŠããå埩åã®ããJavaScriptã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããŸããããªãã¯ä»ãããŒã¿ããã«ããã¯ãé«éãã€ãã©ã€ã³ã«å€ããç¥èã身ã«ã€ããŸãããããã¯ã仿¥ã®ããŒã¿é§ååã®äžçã«ããããã¹ãŠã®éçºè ã«ãšã£ãŠéèŠãªã¹ãã«ã§ãã