ES6 के Promise.all () का उपयोग करते समय संगामिति को सीमित करने का सबसे अच्छा तरीका क्या है?


98

मेरे पास कुछ कोड है जो एक सूची से अधिक है जो डेटाबेस से बाहर निकाली गई थी और उस सूची में प्रत्येक तत्व के लिए एक 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 */});

यह कोड नोड 4.3.2 पर चल रहा है। पुनरावृत्ति करने के लिए, Promise.allप्रबंधित किया जा सकता है ताकि किसी निश्चित समय पर केवल एक निश्चित संख्या में वादे प्रगति पर हों?



3
यह मत भूलो कि Promise.allवादा प्रगति का प्रबंधन करता है - वादे खुद करते हैं, Promise.allबस उनका इंतजार करते हैं।
बर्गी


जवाबों:


51

ध्यान दें कि 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)
})

25
यह दुर्भाग्यपूर्ण है कि es6-वादा-पूल पुनर्निवेशों का उपयोग करने के बजाय वादा करता है। मैं इसके बजाय इस संक्षिप्त समाधान का सुझाव देता हूं (यदि आप पहले से ही ES6 या ES7 का उपयोग कर रहे हैं) github.com/rxaviers/async-pool
राफेल ज़ेवियर

3
दोनों को देख लिया, async- पूल बेहतर लग रहा है! अधिक सीधे आगे और अधिक हल्के।
अंतहीन

2
मैंने पी-लिमिट को भी सबसे सरल कार्यान्वयन पाया है। नीचे मेरा उदाहरण देखें। stackoverflow.com/a/52262024/8177355
मैथ्यू राइडआउट

2
मुझे लगता है कि छोटे-एसाइक-पूल वादों की संगति को सीमित करने के लिए बहुत बेहतर, गैर-दखल देने वाले और बल्कि प्राकृतिक समाधान हैं।
सनी ताम्बी

73

पी-सीमा

मैंने एक कस्टम स्क्रिप्ट, ब्लूबर्ड, es6-वादा-पूल और पी-लिमिट के साथ वादा संगति सीमा की तुलना की है। मेरा मानना ​​है कि इस आवश्यकता के लिए पी-लिमिट में सबसे सरल, स्ट्रिप डाउन कार्यान्वयन है। उनके प्रलेखन देखें

आवश्यकताएँ

उदाहरण के लिए async के साथ संगत होना

मेरा उदाहरण

इस उदाहरण में, हमें सरणी में प्रत्येक URL के लिए एक फ़ंक्शन चलाने की आवश्यकता है (जैसे, शायद एक एपीआई अनुरोध)। यहाँ यह कहा जाता है fetchData()। यदि हमारे पास संसाधित करने के लिए हजारों आइटम हैं, तो निश्चित रूप से सीपीयू और मेमोरी संसाधनों पर सहेजने के लिए समरूपता उपयोगी होगी।

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);
})();

कंसोल लॉग परिणाम आपके हल किए गए वादों प्रतिसाद डेटा का एक सरणी है।


4
इसके लिए धन्यवाद! यह बहुत आसान है
जॉन

3
यह अब तक का सर्वश्रेष्ठ पुस्तकालय है जिसे मैंने एक साथ अनुरोधों को सीमित करने के लिए देखा है। और महान उदाहरण, धन्यवाद!
क्रिस Livdahl

2
तुलना करने के लिए धन्यवाद। क्या आपने github.com/rxaviers/async-pool के खिलाफ तुलना की है ?
आहोंग

1
प्रयोग करने में आसान, बढ़िया विकल्प।
ड्राम्ब्रेवर

22

का उपयोग करते हुए Array.prototype.splice

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

2
यह एक अघोषित समाधान है। सादगी पसंद है।
Brannon

8
यह पूल के बजाय बैचों में कार्य करता है, जहां एक समारोह तुरंत समाप्त हो जाता है जब दूसरा खत्म होता है।
cltsang

यह समाधान पसंद आया!
प्रसून

समझ में आया कि यह उसके चारों ओर अधिक संदर्भ की कमी के साथ क्या कर रहा है, जैसे कि यह उदाहरण के लिए एक पूल के बजाय एक बैच को मार रहा है। आप एरे को पुन: व्यवस्थित कर रहे हैं जो आप शुरुआत से या बीच में विभाजित करते हैं। (ब्राउज़र को सभी आइटमों को फिर से जोड़ना होगा) एक सैद्धांतिक प्रदर्शन बेहतर वैकल्पिक है इसके बजाय अंत से सामान लेना है arr.splice(-100)अगर ऑर्डर की खुराक नहीं आती है, तो शायद आप सरणी को उलट सकते हैं: पी
अंतहीन

बैचों में चलने के लिए बहुत उपयोगी है। नोट: अगला बैच चालू नहीं होगा जब तक कि वर्तमान बैच 100% पूर्ण नहीं हो जाता।
केसी ड्वेन

20

यदि आप जानते हैं कि पुनरावृत्तियाँ कैसे काम करती हैं और उनका उपभोग कैसे किया जाता है, तो आपको किसी अतिरिक्त पुस्तकालय की आवश्यकता नहीं होगी, क्योंकि यह बहुत आसान हो सकता है कि आप स्वयं अपनी संगति का निर्माण कर सकें। मुझे प्रदर्शित करें:

/* [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()एक 2 डी सरणी मिल गया [[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- पूल यह है कि यह दो श्रमिकों को पैदा करता है, इसलिए यदि एक कार्यकर्ता किसी कारण के लिए त्रुटि कहता है तो सूचकांक 5 यह अन्य कार्यकर्ता को बाकी काम करने से नहीं रोकेगा। तो आप 1 से 2 कंसेप्ट करने के लिए नीचे जाते हैं (इसलिए यह वहां नहीं रुकेगा) इसलिए मेरी सलाह है कि आप doWorkफ़ंक्शन के अंदर सभी त्रुटियों को पकड़ें


यह कमाल का है! धन्यवाद अंतहीन!
user3413723

यह निश्चित रूप से एक अच्छा दृष्टिकोण है! बस यह सुनिश्चित करें कि आपकी समसामयिकता आपकी कार्य सूची की लंबाई से अधिक नहीं है (यदि आप वैसे भी परिणामों की परवाह करते हैं), जैसा कि आप अतिरिक्त के साथ समाप्त हो सकते हैं!
क्रिश ओए

कुछ ऐसा जो बाद में ठंडा हो सकता है, जब स्ट्रीम को Readable.from (पुनरावृत्त) समर्थन मिलता है। क्रोम ने पहले ही स्ट्रीम को हस्तांतरणीय बना दिया है । इसलिए आप पठनीय धाराएँ बना सकते हैं और इसे एक वेब कर्मचारी को भेज सकते हैं, और वे सभी एक ही अंतर्निहित पुनरावृत्ति का उपयोग करके समाप्त हो जाएंगे।
अंतहीन

16

Bluebird का Promise.map समानांतर में कितने वादे चलाने चाहिए, यह नियंत्रित करने के लिए एक संगामिति विकल्प ले सकता है। कभी-कभी यह आसान होता है .allक्योंकि आपको वादा सरणी बनाने की आवश्यकता नहीं होती है।

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
}

अगर आप केवल एक ही चीज़ के लिए इसका इस्तेमाल करते हैं तो ब्लूबर्ड को बहुत तेज़ वादों की ज़रूरत है और ~ 18kb अतिरिक्त कबाड़;)
अंतहीन

1
सभी निर्भर करता है कि आपके लिए एक चीज कितनी महत्वपूर्ण है और यदि कोई अन्य तेज़ / आसान बेहतर तरीका है। एक सामान्य व्यापार बंद। मैं कुछ केबी पर उपयोग और कार्य में आसानी का चयन करूंगा, लेकिन YMMV।
झिंगाओ चेन

11

HTTP अनुरोधों को सीमित करने के वादों का उपयोग करने के बजाय, नोड के अंतर्निहित http.Agent.maxSockets का उपयोग करें । यह एक पुस्तकालय का उपयोग करने या अपना स्वयं का पूलिंग कोड लिखने की आवश्यकता को हटाता है, और आपके द्वारा सीमित किए जा रहे लाभों पर अतिरिक्त लाभ होता है।

agent.maxSockets

डिफ़ॉल्ट रूप से इन्फिनिटी के लिए सेट। निर्धारित करता है कि एजेंट कितने मूल सॉकेट्स को मूल प्रति खोल सकता है। उत्पत्ति या तो 'होस्ट: पोर्ट' या 'होस्ट: पोर्ट: लोकलड्रेस' संयोजन है।

उदाहरण के लिए:

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

यदि एक ही मूल के लिए कई अनुरोध किए जा रहे हैं, तो यह आपको keepAliveसच करने के लिए भी सेट कर सकता है (अधिक जानकारी के लिए डॉक्स ऊपर देखें)।


11
फिर भी, तुरंत हजारों क्लोजर बनाना और सॉकेट्स को पूल करना बहुत कुशल प्रतीत नहीं होता है?
बर्गी

3

मेरा सुझाव है कि पुस्तकालय async- पूल: https://github.com/rxaviers/async-pool

npm install tiny-async-pool

विवरण:

देशी ईएस 6 / ईएस 7 का उपयोग करते हुए सीमित संगणना के साथ कई वादे-लौटाने और एसिंक्स फ़ंक्शन चलाएं

asyncPool एक सीमित संगामिति पूल में कई वादा-वापसी और async कार्य करता है। जैसे ही कोई वादे को अस्वीकार करता है, वह तुरंत खारिज कर देता है। यह सभी वादों के पूरा होने पर हल हो जाता है। यह जल्द से जल्द (कंसीडर लिमिट के तहत) इट्रेटर फ़ंक्शन को कॉल करता है।

उपयोग:

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]`.

1
मेरे लिये कार्य करता है। धन्यवाद। यह महान पुस्तकालय है।
सनी ताम्बी

2

इसे पुनरावृत्ति का उपयोग करके हल किया जा सकता है।

विचार यह है कि प्रारंभ में आप अधिकतम स्वीकृत संख्याएँ भेजते हैं और इन अनुरोधों में से प्रत्येक को अपने पूर्ण होने पर पुन: भेजना जारी रखना चाहिए।

function batchFetch(urls, concurrentRequestsLimit) {
    return new Promise(resolve => {
        var documents = [];
        var index = 0;

        function recursiveFetch() {
            if (index === urls.length) {
                return;
            }
            fetch(urls[index++]).then(r => {
                documents.push(r.text());
                if (documents.length === urls.length) {
                    resolve(documents);
                } else {
                    recursiveFetch();
                }
            });
        }

        for (var i = 0; i < concurrentRequestsLimit; i++) {
            recursiveFetch();
        }
    });
}

var sources = [
    'http://www.example_1.com/',
    'http://www.example_2.com/',
    'http://www.example_3.com/',
    ...
    'http://www.example_100.com/'
];
batchFetch(sources, 5).then(documents => {
   console.log(documents);
});

2

यहाँ मेरा ES7 समाधान कॉपी-पेस्ट फ्रेंडली है और संपूर्ण Promise.all()/ map()वैकल्पिक, एक संगामिति सीमा के साथ है।

इसके समान ही Promise.all()यह रिटर्न ऑर्डर के साथ-साथ नॉन वादा रिटर्न वैल्यू के लिए भी कमबैक करता है।

मैंने विभिन्न कार्यान्वयन की तुलना भी शामिल की क्योंकि यह कुछ पहलुओं को दिखाता है कि कुछ अन्य समाधान छूट गए हैं।

प्रयोग

const asyncFn = delay => new Promise(resolve => setTimeout(() => resolve(), delay));
const args = [30, 20, 15, 10];
await asyncPool(args, arg => asyncFn(arg), 4); // concurrency limit of 4

कार्यान्वयन

async function asyncBatch(args, fn, limit = 8) {
  // Copy arguments to avoid side effects
  args = [...args];
  const outs = [];
  while (args.length) {
    const batch = args.splice(0, limit);
    const out = await Promise.all(batch.map(fn));
    outs.push(...out);
  }
  return outs;
}

async function asyncPool(args, fn, limit = 8) {
  return new Promise((resolve) => {
    // Copy arguments to avoid side effect, reverse queue as
    // pop is faster than shift
    const argQueue = [...args].reverse();
    let count = 0;
    const outs = [];
    const pollNext = () => {
      if (argQueue.length === 0 && count === 0) {
        resolve(outs);
      } else {
        while (count < limit && argQueue.length) {
          const index = args.length - argQueue.length;
          const arg = argQueue.pop();
          count += 1;
          const out = fn(arg);
          const processOut = (out, index) => {
            outs[index] = out;
            count -= 1;
            pollNext();
          };
          if (typeof out === 'object' && out.then) {
            out.then(out => processOut(out, index));
          } else {
            processOut(out, index);
          }
        }
      }
    };
    pollNext();
  });
}

तुलना

// A simple async function that returns after the given delay
// and prints its value to allow us to determine the response order
const asyncFn = delay => new Promise(resolve => setTimeout(() => {
  console.log(delay);
  resolve(delay);
}, delay));

// List of arguments to the asyncFn function
const args = [30, 20, 15, 10];

// As a comparison of the different implementations, a low concurrency
// limit of 2 is used in order to highlight the performance differences.
// If a limit greater than or equal to args.length is used the results
// would be identical.

// Vanilla Promise.all/map combo
const out1 = await Promise.all(args.map(arg => asyncFn(arg)));
// prints: 10, 15, 20, 30
// total time: 30ms

// Pooled implementation
const out2 = await asyncPool(args, arg => asyncFn(arg), 2);
// prints: 20, 30, 15, 10
// total time: 40ms

// Batched implementation
const out3 = await asyncBatch(args, arg => asyncFn(arg), 2);
// prints: 20, 30, 20, 30
// total time: 45ms

console.log(out1, out2, out3); // prints: [30, 20, 15, 10] x 3

// Conclusion: Execution order and performance is different,
// but return order is still identical

निष्कर्ष

asyncPool() सबसे अच्छा समाधान होना चाहिए क्योंकि यह किसी भी पिछले एक के खत्म होते ही नए अनुरोधों को शुरू करने की अनुमति देता है।

asyncBatch() एक तुलना के रूप में शामिल किया गया है क्योंकि इसके कार्यान्वयन को समझना आसान है, लेकिन इसे प्रदर्शन में धीमा होना चाहिए क्योंकि अगले बैच को शुरू करने के लिए एक ही बैच में सभी अनुरोधों को समाप्त करना आवश्यक है।

इस वंचित उदाहरण में, गैर-सीमित वैनिला Promise.all()बेशक सबसे तेज है, जबकि अन्य वास्तविक दुनिया की भीड़ के परिदृश्य में अधिक वांछनीय प्रदर्शन कर सकते हैं।

अपडेट करें

एसिंक्स-पूल लाइब्रेरी जो दूसरों ने पहले ही सुझाई है, शायद मेरे कार्यान्वयन का एक बेहतर विकल्प है क्योंकि यह लगभग समान रूप से काम करती है और इसमें Promise.race के चतुर उपयोग के साथ अधिक संक्षिप्त कार्यान्वयन है (): https://github.com/rxaviers/ async-पूल / ब्लॉब / मास्टर / lib / es7.js

उम्मीद है कि मेरा जवाब अभी भी एक शैक्षिक मूल्य परोस सकता है।


1

यहाँ स्ट्रीमिंग और 'पी-लिमिट' के लिए मूल उदाहरण दिया गया है। यह स्ट्रीम मानगो डीबी को http रीड स्ट्रीम स्ट्रीम करता है।

const stream = require('stream');
const util = require('util');
const pLimit = require('p-limit');
const es = require('event-stream');
const streamToMongoDB = require('stream-to-mongo-db').streamToMongoDB;


const pipeline = util.promisify(stream.pipeline)

const outputDBConfig = {
    dbURL: 'yr-db-url',
    collection: 'some-collection'
};
const limit = pLimit(3);

async yrAsyncStreamingFunction(readStream) => {
        const mongoWriteStream = streamToMongoDB(outputDBConfig);
        const mapperStream = es.map((data, done) => {
                let someDataPromise = limit(() => yr_async_call_to_somewhere())

                    someDataPromise.then(
                        function handleResolve(someData) {

                            data.someData = someData;    
                            done(null, data);
                        },
                        function handleError(error) {
                            done(error)
                        }
                    );
                })

            await pipeline(
                readStream,
                JSONStream.parse('*'),
                mapperStream,
                mongoWriteStream
            );
        }

0

इसलिए मैंने अपने कोड के लिए कुछ उदाहरण दिखाए गए काम करने की कोशिश की, लेकिन चूंकि यह केवल एक आयात स्क्रिप्ट और उत्पादन कोड के लिए था, इसलिए npm पैकेज बैच-वादों का उपयोग करना निश्चित रूप से मेरे लिए सबसे आसान रास्ता था।

नोट: प्रोमिस का समर्थन करने या पॉलीफ़िल्ड होने के लिए रनटाइम की आवश्यकता है।

Api बैचप्रोमाइज (int: बैचसाइज़, सरणी: संग्रह, i => वादा: Iteratee) वादा: Iteratee प्रत्येक बैच के बाद बुलाया जाएगा।

उपयोग:

batch-promises
Easily batch promises

NOTE: Requires runtime to support Promise or to be polyfilled.

Api
batchPromises(int: batchSize, array: Collection, i => Promise: Iteratee)
The Promise: Iteratee will be called after each batch.

Use:
import batchPromises from 'batch-promises';
 
batchPromises(2, [1,2,3,4,5], i => new Promise((resolve, reject) => {
 
  // The iteratee will fire after each batch resulting in the following behaviour:
  // @ 100ms resolve items 1 and 2 (first batch of 2)
  // @ 200ms resolve items 3 and 4 (second batch of 2)
  // @ 300ms resolve remaining item 5 (last remaining batch)
  setTimeout(() => {
    resolve(i);
  }, 100);
}))
.then(results => {
  console.log(results); // [1,2,3,4,5]
});


0

यदि आप बाहरी पुस्तकालयों का उपयोग नहीं करना चाहते हैं तो पुनरावृत्ति इसका उत्तर है

downloadAll(someArrayWithData){
  var self = this;

  var tracker = function(next){
    return self.someExpensiveRequest(someArrayWithData[next])
    .then(function(){
      next++;//This updates the next in the tracker function parameter
      if(next < someArrayWithData.length){//Did I finish processing all my data?
        return tracker(next);//Go to the next promise
      }
    });
  }

  return tracker(0); 
}

0

यह वही है जो मैंने उपयोग किया था Promise.race, मेरे कोड के अंदर

const identifyTransactions = async function() {
  let promises = []
  let concurrency = 0
  for (let tx of this.transactions) {
    if (concurrency > 4)
      await Promise.race(promises).then(r => { promises = []; concurrency = 0 })
    promises.push(tx.identifyTransaction())
    concurrency++
  }
  if (promises.length > 0)
    await Promise.race(promises) //resolve the rest
}

यदि आप एक उदाहरण देखना चाहते हैं: https://jsfiddle.net/thecodermarcelo/av2tp83o/5/


2
मैं उस संगति को नहीं कहूंगा ... यह बैच निष्पादन की तरह है ... आप 4 कार्य करते हैं, सभी को समाप्त करने के लिए इंतजार करते हैं और फिर अगले 4 करते हैं। यदि उनमें से एक जल्दी सुलझता है तो आप अन्य 3 के समाप्त होने की प्रतीक्षा करते हैं। , आप का उपयोग क्या होना चाहिएPromise.race
अंतहीन


0
  • @tcooc का जवाब काफी अच्छा था। इसके बारे में नहीं जानते थे और भविष्य में इसका लाभ उठाएंगे।
  • मैंने @MatthewRideout के उत्तर का भी आनंद लिया , लेकिन यह एक बाहरी पुस्तकालय का उपयोग करता है !!

जब भी संभव होता है, मैं पुस्तकालय के लिए जाने के बजाय इस तरह की चीजों को अपने दम पर विकसित करने का एक शॉट देता हूं। आप अंत में बहुत सी अवधारणाएँ सीख रहे हैं जो पहले कठिन थीं।

आप लोग इस प्रयास के बारे में क्या सोचते हैं:
(मैंने इसे बहुत सोचा था और मुझे लगता है कि यह काम कर रहा है, लेकिन यह मत करो कि क्या यह मौलिक रूप से गलत है या नहीं)

 class Pool{
        constructor(maxAsync) {
            this.maxAsync = maxAsync;
            this.asyncOperationsQueue = [];
            this.currentAsyncOperations = 0
        }

        runAnother() {
            if (this.asyncOperationsQueue.length > 0 && this.currentAsyncOperations < this.maxAsync) {
                this.currentAsyncOperations += 1;
                this.asyncOperationsQueue.pop()()
                    .then(() => { this.currentAsyncOperations -= 1; this.runAnother() }, () => { this.currentAsyncOperations -= 1; this.runAnother() })
            }
        }

        add(f){  // the argument f is a function of signature () => Promise
            this.runAnother();
            return new Promise((resolve, reject) => {
                this.asyncOperationsQueue.push(
                    () => f().then(resolve).catch(reject)
                )
            })
        }
    }

//#######################################################
//                        TESTS
//#######################################################

function dbCall(id, timeout, fail) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (fail) {
               reject(`Error for id ${id}`);
            } else {
                resolve(id);
            }
        }, timeout)
    }
    )
}


const dbQuery1 = () => dbCall(1, 5000, false);
const dbQuery2 = () => dbCall(2, 5000, false);
const dbQuery3 = () => dbCall(3, 5000, false);
const dbQuery4 = () => dbCall(4, 5000, true);
const dbQuery5 = () => dbCall(5, 5000, false);


const cappedPool = new Pool(2);

const dbQuery1Res = cappedPool.add(dbQuery1).catch(i => i).then(i => console.log(`Resolved: ${i}`))
const dbQuery2Res = cappedPool.add(dbQuery2).catch(i => i).then(i => console.log(`Resolved: ${i}`))
const dbQuery3Res = cappedPool.add(dbQuery3).catch(i => i).then(i => console.log(`Resolved: ${i}`))
const dbQuery4Res = cappedPool.add(dbQuery4).catch(i => i).then(i => console.log(`Resolved: ${i}`))
const dbQuery5Res = cappedPool.add(dbQuery5).catch(i => i).then(i => console.log(`Resolved: ${i}`))

यह दृष्टिकोण स्कैला / जावा में थ्रेड पूल के समान एक अच्छा एपीआई प्रदान करता है।
के साथ पूल का एक उदाहरण बनाने के बाद const cappedPool = new Pool(2), आप इसके साथ बस वादे प्रदान करते हैं cappedPool.add(() => myPromise)
निस्संदेह हमें यह सुनिश्चित करना चाहिए कि वादा तुरंत शुरू नहीं होता है और इसीलिए हमें फ़ंक्शन की सहायता से "इसे आसानी से प्रदान करना चाहिए"।

सबसे महत्वपूर्ण बात, ध्यान दें कि विधि add का परिणाम एक वादा है जो आपके मूल वादे के मूल्य के साथ पूरा / हल किया जाएगा ! यह एक बहुत ही सहज उपयोग के लिए बनाता है।

const resultPromise = cappedPool.add( () => dbCall(...))
resultPromise
.then( actualResult => {
   // Do something with the result form the DB
  }
)

0

दुर्भाग्य से देशी Promise.all के साथ ऐसा करने का कोई तरीका नहीं है, इसलिए आपको रचनात्मक होना होगा।

यह किसी भी बाहरी पुस्तकालयों का उपयोग किए बिना मुझे प्राप्त करने का सबसे तेज़ तरीका है।

यह एक नई जावास्क्रिप्ट सुविधा का उपयोग करता है जिसे इट्रेटर कहा जाता है। यह मूल रूप से इस बात का ट्रैक रखता है कि किन वस्तुओं को संसाधित किया गया है और क्या नहीं।

कोड में इसका उपयोग करने के लिए, आप async फ़ंक्शन की एक सरणी बनाते हैं। प्रत्येक async फ़ंक्शन अगले आइटम के लिए एक ही पुनरावृत्ति पूछता है जिसे संसाधित करने की आवश्यकता होती है। प्रत्येक फ़ंक्शन अपने स्वयं के आइटम को अतुल्यकालिक रूप से संसाधित करता है, और जब किया जाता है तो नए के लिए पुनरावृत्तिकर्ता से पूछता है। एक बार जब पुनरावृत्त आइटम से बाहर चला जाता है, तो सभी कार्य पूरे हो जाते हैं।

@ प्रेरणा के लिए धन्यवाद।

var items = [
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
];

var concurrency = 5

Array(concurrency).fill(items.entries()).map(async (cursor) => {
    for(let [index, url] of cursor){
        console.log("getting url is ", index, url);
        // run your async task instead of this next line
        var text = await fetch(url).then(res => res.text());
        console.log("text is", text.slice(0,20));
    }
})


क्यों यह नीचे चिह्नित के रूप में उत्सुक। यह बहुत कुछ वैसा ही है जैसा मैं लेकर आया हूं।
क्रिश ओये

0

इतने सारे अच्छे उपाय। मैंने @Endless द्वारा पोस्ट किए गए सुरुचिपूर्ण समाधान के साथ शुरुआत की और इस छोटे से विस्तार की विधि के साथ समाप्त हो गया, जिसमें किसी भी बाहरी पुस्तकालय का उपयोग नहीं किया गया है और न ही यह बैचों में चलता है (हालांकि मानता है कि आपके पास async, आदि जैसी विशेषताएं हैं):

Promise.allWithLimit = async (taskList, limit = 5) => {
    const iterator = taskList.entries();
    let results = new Array(taskList.length);
    let workerThreads = new Array(limit).fill(0).map(() => 
        new Promise(async (resolve, reject) => {
            try {
                let entry = iterator.next();
                while (!entry.done) {
                    let [index, promise] = entry.value;
                    try {
                        results[index] = await promise;
                        entry = iterator.next();
                    }
                    catch (err) {
                        results[index] = err;
                    }
                }
                // No more work to do
                resolve(true); 
            }
            catch (err) {
                // This worker is dead
                reject(err);
            }
        }));

    await Promise.all(workerThreads);
    return results;
};

    Promise.allWithLimit = async (taskList, limit = 5) => {
        const iterator = taskList.entries();
        let results = new Array(taskList.length);
        let workerThreads = new Array(limit).fill(0).map(() => 
            new Promise(async (resolve, reject) => {
                try {
                    let entry = iterator.next();
                    while (!entry.done) {
                        let [index, promise] = entry.value;
                        try {
                            results[index] = await promise;
                            entry = iterator.next();
                        }
                        catch (err) {
                            results[index] = err;
                        }
                    }
                    // No more work to do
                    resolve(true); 
                }
                catch (err) {
                    // This worker is dead
                    reject(err);
                }
            }));
    
        await Promise.all(workerThreads);
        return results;
    };

    const demoTasks = new Array(10).fill(0).map((v,i) => new Promise(resolve => {
       let n = (i + 1) * 5;
       setTimeout(() => {
          console.log(`Did nothing for ${n} seconds`);
          resolve(n);
       }, n * 1000);
    }));

    var results = Promise.allWithLimit(demoTasks);

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.