วิธีที่ดีที่สุดในการ จำกัด การทำงานพร้อมกันเมื่อใช้ Promise.all () ของ ES6 คืออะไร


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เพียงแค่รอคอยพวกเขา
Bergi


คำตอบ:


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-prom-pool สร้าง Promise ขึ้นมาใหม่แทนที่จะใช้มัน ฉันขอแนะนำวิธีแก้ปัญหาที่กระชับแทน (หากคุณใช้ ES6 หรือ ES7 อยู่แล้ว) github.com/rxaviers/async-pool
Rafael Xavier

3
ลองดูทั้งสองแบบ async-pool ดูดีกว่า! ตรงไปตรงมามากขึ้นและมีน้ำหนักเบามากขึ้น
สิ้นสุด

2
ฉันยังพบว่า p-limit เป็นการใช้งานที่ง่ายที่สุด ดูตัวอย่างของฉันด้านล่าง stackoverflow.com/a/52262024/8177355
Matthew Rideout

2
ฉันคิดว่า small-asyc-pool นั้นดีกว่ามากไม่ล่วงล้ำและเป็นวิธีแก้ปัญหาที่ค่อนข้างเป็นธรรมชาติสำหรับการ จำกัด การทำสัญญาพร้อมกัน
Sunny Tambi

73

พี - ลิมิต

ฉันได้เปรียบเทียบข้อ จำกัด การทำงานพร้อมกันของสัญญากับสคริปต์ที่กำหนดเอง, bluebird, es6-prom-pool และ p-limit ฉันเชื่อว่าp-limitมีการใช้งานที่ง่ายที่สุดและถูกถอดออกสำหรับความต้องการนี้ ดูเอกสารของพวกเขา

ข้อกำหนด

เพื่อให้เข้ากันได้กับ async ในตัวอย่าง

ตัวอย่างของฉัน

ในตัวอย่างนี้เราจำเป็นต้องเรียกใช้ฟังก์ชันสำหรับทุก 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);
})();

ผลลัพธ์บันทึกคอนโซลคืออาร์เรย์ของข้อมูลการตอบสนองที่สัญญาที่คุณแก้ไขแล้ว


4
ขอบคุณสำหรับสิ่งนี้! อันนี้ง่ายกว่ามาก
John

3
นี่เป็นไลบรารีที่ดีที่สุดที่ฉันเคยเห็นสำหรับการ จำกัด คำขอพร้อมกัน และตัวอย่างที่ดีขอบคุณ!
Chris Livdahl

2
ขอบคุณที่ทำการเปรียบเทียบ คุณได้เปรียบเทียบกับgithub.com/rxaviers/async-poolหรือไม่?
Ahong

1
ใช้งานง่ายเป็นทางเลือกที่ดี
drmrbrewer

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

ชอบวิธีนี้!
prasun

ใช้เวลาสักครู่เพื่อทำความเข้าใจว่ากำลังทำอะไรอยู่โดยขาดบริบทรอบ ๆ ตัวมากขึ้นเช่นมันเป็นกลุ่มแทนที่จะเป็นสระว่ายน้ำ คุณจัดลำดับอาร์เรย์ใหม่ทุกครั้งที่คุณแยกจากจุดเริ่มต้นหรือตรงกลาง (เบราว์เซอร์ต้องจัดทำดัชนีรายการทั้งหมดอีกครั้ง) ประสิทธิภาพทางทฤษฎีที่ดีกว่าทางเลือกคือการรับสิ่งต่างๆจากจุดสิ้นสุดแทนarr.splice(-100)หากปริมาณการสั่งซื้อไม่กลายเป็นเนื้อเดียวกันคุณอาจสามารถย้อนกลับอาร์เรย์ได้: P
ไม่มีที่สิ้นสุด

มีประโยชน์มากสำหรับการทำงานเป็นแบทช์ หมายเหตุ: ชุดต่อไปจะไม่เริ่มต้นจนกว่าชุดปัจจุบันจะเสร็จสมบูรณ์ 100%
Casey Dwayne

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

เราสามารถใช้ iterator เดียวกันและแชร์กับคนทำงานได้

หากคุณใช้.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-poolตัวอย่างคือมันสร้างคนงานสองคนดังนั้นหากคนงานคนหนึ่งเกิดข้อผิดพลาดด้วยเหตุผลบางประการที่ดัชนี 5 ดังกล่าวจะไม่หยุดคนงานคนอื่น ๆ จากการทำงานที่เหลือ ดังนั้นคุณจึงเปลี่ยนจากการทำ 2 พร้อมกันลงมาเป็น 1 (ดังนั้นมันจะไม่หยุดอยู่แค่นั้น) ดังนั้นคำแนะนำของฉันคือคุณจับข้อผิดพลาดทั้งหมดภายในdoWorkฟังก์ชัน


นี่มันเจ๋งมาก! ขอบคุณไม่รู้จบ!
user3413723

นี่เป็นแนวทางเด็ดแน่นอน! ตรวจสอบให้แน่ใจว่าการทำงานพร้อมกันของคุณไม่เกินความยาวของรายการงานของคุณ (หากคุณสนใจผลลัพธ์อยู่แล้ว) เพราะคุณอาจได้รับความพิเศษ!
Kris Oye

สิ่งที่อาจจะเจ๋งกว่าในภายหลังคือเมื่อ Streams ได้รับการสนับสนุนReadable.from (iterator) Chrome ได้ทำให้สตรีมสามารถโอนได้แล้ว เพื่อให้คุณสามารถสร้างสตรีมที่อ่านได้และส่งออกไปยังผู้ปฏิบัติงานบนเว็บและพวกเขาทั้งหมดจะจบลงด้วยการใช้ตัววนซ้ำพื้นฐานเดียวกัน
ไม่มีที่สิ้นสุด

16

Promise.mapของ bluebird สามารถใช้ตัวเลือกการทำงานพร้อมกันเพื่อควบคุมจำนวนสัญญาที่ควรทำงานควบคู่กัน บางครั้งมันง่ายกว่า.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
}

bluebird เป็นเรื่องที่น่ายินดีหากคุณต้องการสัญญาที่เร็วขึ้นและขยะพิเศษ ~ 18kb หากคุณใช้เพียงสิ่งเดียวเท่านั้น)
ไม่มีที่สิ้นสุด

1
ทั้งหมดขึ้นอยู่กับความสำคัญของสิ่งหนึ่งสำหรับคุณและหากมีวิธีอื่นที่เร็วกว่า / ง่ายกว่า การแลกเปลี่ยนโดยทั่วไป ฉันจะเลือกใช้งานง่ายและใช้งานได้มากกว่าไม่กี่กิโลไบต์ แต่ YMMV
Jingshao Chen

11

แทนการใช้สัญญาสำหรับการ จำกัด การร้องขอ http ใช้โหนดในตัวhttp.Agent.maxSockets สิ่งนี้จะลบข้อกำหนดในการใช้ไลบรารีหรือการเขียนโค้ดพูลของคุณเองและมีข้อดีเพิ่มเติมในการควบคุมสิ่งที่คุณ จำกัด

agent.maxSockets

โดยค่าเริ่มต้นตั้งไว้ที่ Infinity กำหนดจำนวนซ็อกเก็ตพร้อมกันที่เอเจนต์สามารถเปิดได้ต่อจุดเริ่มต้น 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เป็นจริง (ดูเอกสารด้านบนสำหรับข้อมูลเพิ่มเติม)


11
กระนั้นการสร้างการปิดหลายพันครั้งในทันทีและการรวมซ็อกเก็ตดูเหมือนจะไม่มีประสิทธิภาพมากนัก?
Bergi

3

ฉันแนะนำห้องสมุด async-pool: https://github.com/rxaviers/async-pool

npm install tiny-async-pool

คำอธิบาย:

เรียกใช้ฟังก์ชันการคืนสัญญา & async หลายรายการพร้อมกัน จำกัด โดยใช้ ES6 / ES7 ดั้งเดิม

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
ใช้ได้ผลสำหรับฉัน ขอบคุณ. นี่คือห้องสมุดที่ยอดเยี่ยม
Sunny Tambi

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()แน่นอนว่านั้นเร็วที่สุดในขณะที่ชนิดอื่น ๆ สามารถทำงานได้เป็นที่ต้องการมากกว่าในสถานการณ์ความแออัดในโลกแห่งความเป็นจริง

อัปเดต

ไลบรารี async-pool ที่คนอื่นแนะนำไปแล้วน่าจะเป็นทางเลือกที่ดีกว่าสำหรับการใช้งานของฉันเนื่องจากทำงานได้เกือบเหมือนกันและมีการใช้งานที่รัดกุมยิ่งขึ้นด้วยการใช้ Promise.race () อย่างชาญฉลาด: https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js

หวังว่าคำตอบของฉันจะยังคงให้คุณค่าทางการศึกษา


1

ต่อไปนี้เป็นตัวอย่างพื้นฐานสำหรับการสตรีมและ 'p-limit' สตรีม http อ่านสตรีมไปยัง mongo db

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 จึงเป็นเส้นทางที่ง่ายที่สุดสำหรับฉัน

หมายเหตุ: ต้องใช้รันไทม์เพื่อรองรับ Promise หรือจะเป็นแบบ polyfilled

Api batchPromises (int: batchSize, array: Collection, i => Promise: Iteratee) The Promise: 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}`))

วิธีนี้ให้ API ที่ดีคล้ายกับเธรดพูลใน scala / java
หลังจากที่สร้างหนึ่งตัวอย่างของสระว่ายน้ำที่มีคุณให้สัญญาว่าจะด้วยเพียง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 แต่ละฟังก์ชันจะถามตัววนซ้ำเดียวกันสำหรับรายการถัดไปที่ต้องดำเนินการ แต่ละฟังก์ชันจะประมวลผลรายการของตัวเองแบบอะซิงโครนัสและเมื่อเสร็จสิ้นจะขอให้ตัววนซ้ำสำหรับรายการใหม่ เมื่อตัววนรอบรายการหมดฟังก์ชันทั้งหมดจะสมบูรณ์

ขอบคุณ @Endless สำหรับแรงบันดาลใจ

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


อยากรู้ว่าทำไมถึงทำเครื่องหมายลง มันคล้ายกับสิ่งที่ฉันคิดขึ้นมา
Kris Oye

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.