ES6's Promise.all () kullanırken eşzamanlılığı sınırlamanın en iyi yolu nedir?


99

Veritabanından sorgulanan bir liste üzerinde yinelenen ve bu listedeki her öğe için bir HTTP isteğinde bulunan bazı kodum var. Bu liste bazen oldukça büyük bir sayı olabilir (binler içinde) ve binlerce eşzamanlı HTTP isteğiyle bir web sunucusuna çarpmadığımdan emin olmak istiyorum.

Bu kodun kısaltılmış versiyonu şu anda şuna benzer ...

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

Bu kod Düğüm 4.3.2'de çalışıyor. Tekrarlamak gerekirse, herhangi bir Promise.allzamanda yalnızca belirli sayıda Sözün işlenmesi için yönetilebilir mi?



3
Unutmayın ki Promise.allbu , vaatlerin ilerlemesini yönetir - vaatler bunu kendileri yapar, Promise.allsadece onları bekler.
Bergi


Yanıtlar:


51

Bunun Promise.all()işe başlama sözlerini tetiklemediğini unutmayın , sözün kendisi yaratır.

Bunu akılda tutarak, bir çözüm söz konusu olduğunda yeni bir sözün başlatılması gerekip gerekmediğini veya zaten sınırda olup olmadığınızı kontrol etmektir.

Ancak, burada tekerleği yeniden icat etmeye gerçekten gerek yok. Bu amaçla kullanabileceğiniz bir kitaplıkes6-promise-pool . Örneklerinden:

// 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-promise-pool'un Promise'ı kullanmak yerine yeniden icat etmesi talihsiz bir durum. Bunun yerine bu kısa çözümü öneriyorum (zaten ES6 veya ES7 kullanıyorsanız) github.com/rxaviers/async-pool
Rafael Xavier

3
İkisine de bir göz attı, eşzamansız havuz çok daha iyi görünüyor! Daha yalın ve daha hafif.
sonsuz

2
Ayrıca p-limitini en basit uygulama olarak buldum. Aşağıdaki örneğime bakın. stackoverflow.com/a/52262024/8177355
Matthew Rideout

2
Bence minik-asyc-pool, vaatlerin eşzamanlılığını sınırlamak için çok daha iyi, müdahaleci olmayan ve oldukça doğal bir çözüm.
Sunny Tambi

74

P-Limit

Eşzamanlı vaat sınırlamasını özel bir betik, bluebird, es6-söz-havuzu ve p-limiti ile karşılaştırdım. Bu ihtiyaç için p-limit'in en basit, basitleştirilmiş uygulamaya sahip olduğuna inanıyorum . Belgelerine bakın .

Gereksinimler

Örnekte eşzamansız ile uyumlu olmak

Örneğim

Bu örnekte, dizideki her URL için bir işlev çalıştırmamız gerekir (örneğin, bir API isteği gibi). İşte buna denir fetchData(). İşlenecek binlerce öğe dizimiz olsaydı, eşzamanlılık kesinlikle CPU ve bellek kaynaklarından tasarruf etmek için yararlı olurdu.

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

Konsol günlüğü sonucu, çözülmüş vaatler yanıt verilerinizin bir dizisidir.


4
Bunun için teşekkürler! Bu çok daha basit
John

3
Bu, eşzamanlı istekleri sınırlamak için gördüğüm en iyi kitaplıktı. Ve harika bir örnek, teşekkürler!
Chris Livdahl

2
Karşılaştırmayı yaptığınız için teşekkürler. Github.com/rxaviers/async-pool ile karşılaştırdınız mı ?
zamanda

1
Kullanımı kolay, harika seçim.
drmrbrewer

22

Kullanma Array.prototype.splice

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

2
Bu, az değer verilen bir çözümdür. Sadeliği seviyorum.
Brannon

8
Bu, işlevleri havuz yerine gruplar halinde çalıştırır; burada bir işlev, diğeri tamamlandığında hemen çağrılır.
cltsang

Bu çözümü sevdim!
prasun

Bir havuz yerine toplu iş yapmak gibi, etrafında daha fazla bağlam olmadığı için ne yaptığını kavramak bir saniye sürdü. Baştan veya ortada her ekleme yaptığınızda diziyi yeniden sıralıyorsunuz. (tarayıcının tüm öğeleri yeniden indekslemesi gerekir) teorik bir performans daha iyi bir alternatif, arr.splice(-100)sipariş dozu uygun değilse, bunun yerine sondan bir şeyler almaktır , belki diziyi tersine çevirebilirsiniz: P
Endless

Gruplar halinde çalışmak için çok kullanışlıdır. Not: sonraki parti, mevcut parti% 100 tamamlanana kadar başlamayacaktır.
Casey Dwayne

21

Yineleyicilerin nasıl çalıştığını ve nasıl tüketildiğini biliyorsanız, kendi eşzamanlılığınızı kendiniz oluşturmak çok kolay olabileceğinden fazladan bir kitaplığa ihtiyacınız olmaz. Göstermeme izin verin:

/* [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)
  }
}

Aynı yineleyiciyi kullanabilir ve çalışanlar arasında paylaşabiliriz.

Sizin .entries()yerine kullanmış .values()olsaydınız, [[index, value]]aşağıda 2 eşzamanlılık ile göstereceğim bir 2D dizi elde ederdiniz .

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

Bunun yararı, her şeyi aynı anda hazır bulundurmak yerine bir jeneratör işlevine sahip olabilmenizdir .


Not: Eşzamansız havuz örneğinden farklı olan şey, iki işçi üretmesidir, bu nedenle bir işçi herhangi bir nedenle indeks 5'de bir hata atarsa, diğer çalışanın geri kalanını yapmasını engellemez. Yani 2 eşzamanlılık yapmaktan 1'e inersiniz (böylece orada durmaz) Yani benim tavsiyem, doWorkişlevin içindeki tüm hataları yakalamanızdır.


Bu harika! Sonsuz teşekkürler!
user3413723

Bu kesinlikle harika bir yaklaşım! Sadece eşzamanlılığınızın görev listenizin uzunluğunu aşmadığından emin olun (yine de sonuçları önemsiyorsanız), çünkü ekstralarla sonuçlanabilirsiniz!
Kris Oye

Daha sonra daha serin olabilecek bir şey, Akışların Okunabilir hale gelmesidir . Chrome, akışları zaten aktarılabilir hale getirdi . böylece okunabilir akışlar oluşturabilir ve bir web çalışanına gönderebilirsiniz ve bunların tümü aynı temel yineleyiciyi kullanır.
Sonsuz

16

bluebird's Promise.map , paralel olarak kaç adet vaat olması gerektiğini kontrol etmek için bir eşzamanlılık seçeneği alabilir. Bazen bu, .allpromise dizisini oluşturmanız gerekmediğinden daha kolaydır .

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, daha hızlı sözlere ihtiyacınız varsa minnettar ve yalnızca bir şey için kullanırsanız ~ 18kb ekstra önemsiz;)
Endless

1
Her şey, bir şeyin sizin için ne kadar önemli olduğuna ve daha hızlı / kolay başka bir yol olup olmadığına bağlıdır. Tipik bir değiş tokuş. Birkaç kb'nin üzerinde kullanım kolaylığı ve işlevi seçeceğim, ancak YMMV.
Jingshao Chen

11

Http isteklerini sınırlamak için sözler kullanmak yerine, düğümün yerleşik http.Agent.maxSockets'ini kullanın . Bu, bir kitaplık kullanma veya kendi havuzlama kodunuzu yazma gereksinimini ortadan kaldırır ve neyi sınırladığınız üzerinde daha fazla kontrol sahibi olma avantajına sahiptir.

agent.maxSockets

Varsayılan olarak Infinity olarak ayarlanmıştır. Aracının kaynak başına kaç eşzamanlı sokete sahip olabileceğini belirler. Kaynak, bir 'ana bilgisayar: bağlantı noktası' veya 'ana bilgisayar: bağlantı noktası: yerelAdres' kombinasyonudur.

Örneğin:

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

Aynı menşe için birden fazla istekte bulunuyorsanız, keepAlivedoğru olarak ayarlamanız da faydalı olabilir (daha fazla bilgi için yukarıdaki belgelere bakın).


11
Yine de, anında binlerce kapatma oluşturmak ve yuvaları havuzlamak çok verimli görünmüyor mu?
Bergi

3

Kütüphane asenkron havuzunu öneririm: https://github.com/rxaviers/async-pool

npm install tiny-async-pool

Açıklama:

Yerel ES6 / ES7'yi kullanarak sınırlı eşzamanlılıkla birden çok vaat getiren ve eşzamansız işlevi çalıştırın

asyncPool, sınırlı bir eşzamanlılık havuzunda birden çok söz veren ve eşzamansız işlev çalıştırır. Vaatlerden biri reddeder reddedilmez reddeder. Tüm sözler tamamlandığında çözülür. Yineleyici işlevini mümkün olan en kısa sürede çağırır (eşzamanlılık sınırı altında).

Kullanım:

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
Benim için çalışıyor. Teşekkürler. Bu harika bir kütüphane.
Sunny Tambi

2

Özyineleme kullanılarak çözülebilir.

Buradaki fikir, başlangıçta izin verilen maksimum sayıda istek göndermeniz ve bu isteklerden her birinin, tamamlandığında kendisini tekrar tekrar göndermeye devam etmesi gerektiğidir.

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

İşte bir kopyala-yapıştır dostu ve eşzamanlılık sınırı olan eksiksiz Promise.all()/ map()alternatif özellikli ES7 çözümüm .

Buna benzer şekilde Promise.all(), iade emrini ve taahhüt dışı dönüş değerleri için bir geri dönüşü korur.

Ayrıca, diğer çözümlerden birkaçının kaçırdığı bazı yönleri gösterdiği için farklı uygulamaların bir karşılaştırmasını da dahil ettim.

Kullanım

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

Uygulama

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

Karşılaştırma

// 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

Sonuç

asyncPool() Yeni isteklerin önceki istek biter bitmez başlamasına izin verdiği için en iyi çözüm olmalıdır.

asyncBatch() uygulaması anlaşılması daha kolay olduğu için karşılaştırma olarak dahil edilmiştir, ancak bir sonraki partiyi başlatmak için aynı partideki tüm isteklerin tamamlanması gerektiğinden performans açısından daha yavaş olmalıdır.

Bu uydurma örnekte, sınırlı olmayan vanilya Promise.all()elbette en hızlısıdır, diğerleri ise gerçek dünyadaki tıkanıklık senaryosunda daha arzu edilir bir performans sergileyebilir.

Güncelleme

Başkalarının zaten önerdiği zaman uyumsuz havuz kitaplığı, neredeyse aynı şekilde çalıştığı ve Promise.race () 'nin akıllıca kullanımıyla daha kısa bir uygulamaya sahip olduğu için muhtemelen benim uygulamama daha iyi bir alternatiftir: https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js

Umarım cevabım hala eğitimsel bir değere hizmet edebilir.


1

İşte akış ve 'p-limiti' için temel örnek. Mongo db'ye http okuma akışını aktarır.

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

Bu yüzden gösterilen bazı örneklerin kodum için çalışmasını sağlamaya çalıştım, ancak bu yalnızca bir içe aktarma komut dosyası için olduğu ve üretim kodu için olmadığı için, npm paketi toplu vaatlerini kullanmak kesinlikle benim için en kolay yoldur

NOT: Promise'i desteklemek veya çoklu doldurulmak için çalışma zamanı gerektirir.

Api batchPromises (int: batchSize, array: Collection, i => Promise: Iteratee) Promise: Iteratee her partiden sonra çağrılacaktır.

Kullanım:

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

Harici kitaplıkları kullanmak istemiyorsanız, özyineleme cevaptır

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

Bunu kullanarak yaptığım şey bu, Promise.raceburadaki kodumun içinde

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
}

Bir örnek görmek isterseniz: https://jsfiddle.net/thecodermarcelo/av2tp83o/5/


2
Ben buna eşzamanlılık demezdim ... Bu daha çok toplu yürütme gibi ... 4 görev yaparsınız, hepsinin bitmesini beklersiniz ve sonra bir sonraki 4'ü yaparsınız , kullanmanız gereken şeyPromise.race
Endless


0
  • @tcooc'un cevabı oldukça iyiydi. Bunu bilmiyordum ve gelecekte bundan yararlanacak.
  • @MatthewRideout'un cevabını da beğendim , ancak harici bir kitaplık kullanıyor !!

Mümkün olduğunda, bu tür şeyleri bir kütüphaneye gitmek yerine kendi başıma geliştirmek için bir şans veriyorum. Daha önce göz korkutucu görünen birçok kavramı öğrenmeye başladınız.

Siz bu girişim hakkında ne düşünüyorsunuz:
(Çok düşündüm ve işe yaradığını düşünüyorum, ancak olmadığını veya temelde yanlış bir şey olup olmadığını belirtin)

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

Bu yaklaşım scala / java'daki iş parçacığı havuzlarına benzer güzel bir API sağlar.
Havuzun bir örneğini oluşturduktan sonra const cappedPool = new Pool(2), ona basitçe vaatler vermiş olursunuz cappedPool.add(() => myPromise).
Açıktır ki, sözün hemen başlamamasını sağlamalıyız ve bu nedenle, işlevin yardımıyla "tembelce vermeliyiz".

En önemlisi, yöntemin sonucunun, add orijinal sözünüzün değeri ile tamamlanacak / çözülecek bir Söz olduğuna dikkat edin ! Bu çok sezgisel bir kullanım sağlar.

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

0

Ne yazık ki bunu yerel Promise.all ile yapmanın bir yolu yok, bu yüzden yaratıcı olmanız gerekiyor.

Bu, herhangi bir dış kitaplık kullanmadan bulabildiğim en kısa ve öz yol.

Yineleyici adı verilen daha yeni bir javascript özelliğini kullanır. Yineleyici temel olarak hangi öğelerin işlendiğini ve nelerin işlenmediğini takip eder.

Kodda kullanmak için bir dizi zaman uyumsuz işlev oluşturursunuz. Her zaman uyumsuz işlev, işlenmesi gereken bir sonraki öğe için aynı yineleyiciye sorar. Her işlev kendi öğesini eşzamansız olarak işler ve tamamlandığında yineleyiciden yeni bir tane ister. Yineleyicide öğeler bittiğinde, tüm işlevler tamamlanır.

İlham için @ Sonsuz'a teşekkürler.

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


Bunun neden işaretlendiğini merak ediyorum. Benim bulduğuma çok benziyor.
Kris Oye

0

Pek çok iyi çözüm. @Endless tarafından yayınlanan zarif çözümle başladım ve herhangi bir harici kitaplık kullanmayan veya gruplar halinde çalışmayan bu küçük genişletme yöntemiyle sonuçlandım (eşzamansız, vb. Gibi özelliklere sahip olduğunuzu varsaysa da):

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


0

@deceleratedcaviar tarafından gönderilen yanıtı genişleterek, bağımsız değişken olarak alan bir 'toplu' yardımcı program işlevi oluşturdum: değerler dizisi, eşzamanlılık sınırı ve işleme işlevi. Evet, Promise.all'ı bu şekilde kullanmanın gerçek eşzamanlılık yerine toplu işlemeye daha çok benzediğini anlıyorum, ancak amaç bir seferde aşırı sayıda HTTP çağrısını sınırlamaksa, basitliği ve harici kitaplığa gerek olmaması nedeniyle bu yaklaşımı kullanıyorum .

async function batch(o) {
  let arr = o.arr
  let resp = []
  while (arr.length) {
    let subset = arr.splice(0, o.limit)
    let results = await Promise.all(subset.map(o.process))
    resp.push(results)
  }
  return [].concat.apply([], resp)
}

let arr = []
for (let i = 0; i < 250; i++) { arr.push(i) }

async function calc(val) { return val * 100 }

(async () => {
  let resp = await batch({
    arr: arr,
    limit: 100,
    process: calc
  })
  console.log(resp)
})();

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.