Ma tâche : j'ai un fichier qui contient de nombreux éléments et chaque élément est lié à un tableau d'URL d'images que je dois télécharger. Je souhaite télécharger tous les liens, j'utilise cette bibliothèque pour l'image le téléchargement et j'utilise des promesses.
Le problème: Le problème se produit lorsque je commence à télécharger de nombreuses images à partir de nombreux éléments, le programme envoie plus de 4000 requêtes avant la fin de la première et le programme plante.
Ma solution: Mon idée était de ne gérer qu'environ 2 éléments à la fois afin que je télécharge environ 20 images à la fois. J'ai essayé toutes sortes de variantes avec des promesses et des fonctions asynchrones, mais je suis assez novice dans ce domaine donc mes tentatives ont échoué.
Mon flux de code est quelque chose comme ceci:
csvRun() function csvRun(){ for(let i = 1; i <= itemsAmount; i++){ // Loops over the items // I want to be able to run only x items at a time console.log('Item number ' + i) itemHandle() } } function itemHandle(){ // This function seems useless here but since the item has more data I kept it here handleImages() } function handleImages(){ // Loops over the images of the item for(let g = 0; g < imagesAmount; g++){ // Here there is a promise that downloads images // For the example I'll use settimout setTimeout(() => { console.log('Image downloaded ' + g) }, 3000); /** If you want the error just use ImgDonwload instead of settimeout and set imagesAmount to 20 and itemsAmount to 400 */ } } // Only here to recreate the error. Not necessarily relevant. function ImgDownload(){ var download = require('image-downloader') download // returns the promise so the handling could resume in order .image({ url: "https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg", dest: "/folder/img.jpg" }) .then(({ filename, image }) => { console.log("File saved to", filename); }) .catch((err: Error) => { console.error(err); }); }
4 Réponses :
Une option serait d'avoir une boucle qui parcourt les images et les traite les unes après les autres. Pour exécuter ensuite plusieurs traitements en parallèle, lancez plusieurs boucles:
const delay = ms => new Promise(res => setTimeout(res, ms)); inParallel(async time => { console.log(`Timer for ${time}ms starts`); await delay(time); console.log(`Timer for ${time}ms ends`); }, [5000, 6000, 1000]/*ms*/, 2/*in parallel*/);
À utiliser comme:
// Goes over the "data" array, calls and waits for each "task" and processes "runnerCount" tasks in parallel function inParallel(task, data, runnerCount) { let i = 0, results = []; async function runner() { while(i < data.length) { const pos = i++; // be aware: concurrent modification of i const entry = data[pos]; results[pos] = await task(entry); } } const runners = Array.from({ length: runnerCount }, runner); return Promise.all(runners).then(() => results); }
Wow, quelle technique fascinante! Pourriez-vous publier ceci sur Parallélisme limité avec async / await ? Le seul inconvénient est qu'il continue de fonctionner après le rejet d'une promesse.
Array.from ({length: runnerCount}, runner)
:-)
@bergi en effet c'est mieux. Je suis également intéressé par votre opinion sur es.discourse.group/t/...
Et concernant la gestion des erreurs: try {...} catch {i = Infinity; }
pourrait fonctionner
Comment obtenir le tableau de résultats?
@ Koby27 le résultat de ceci devrait être un tableau const results = inParallel (array, () => ())
La publication croisée @bergi semble être découragée, un mod vient de supprimer l'autre réponse.
@JonasWilms Euh, oui, vous ne devriez pas simplement copier-coller la réponse, vous devrez l'adapter à l'autre question (plus générique). Fondamentalement, écrire une nouvelle réponse à cette question, en utilisant la même technique de solution.
@JonasWilms Oui, je suppose que c'était le problème. Les mods iirc sont également notifiés lorsque des copies 1: 1 des réponses sont publiées
avec des promesses vanille, vous pouvez faire quelque chose comme:
const MAX_CONCURRENT = 2 const fetchLoop = async () => { while (items.length > 0) { const response = await doFetch(items.pop()) // do stuff with the response } } for (let i = 0; i < MAX_CONCURRENT; ++i) fetchLoop()
avec async / wait quelque chose comme:
let pending_fetches = 0 const MAX_CONCURRENT = 2 const fetch_interval = setInterval(() => { if (items.length === 0) return clearInterval(fetch_interval) if (pending_fetches < MAX_CONCURRENT) { ++pending_fetches doFetch(items.pop()).then(response => { // do stuff with the response --pending_fetches }) } }, 100)
Supposons que vos données ressemblent à ceci
// this function will try to download images per items const download = require('image-downloader') const downloadImages = async (items = []) => { let promises = []; for (const item of items) { const images = item.images; // dest is item.id/imageIndex.jpg promsies = images.map((url, index) => download({url, dest: `/folder/${item.id}/${index}.jpg`})); await Promise.all(promises); } } downloadImages(items);
Je lancerais une simple boucle for..of, j'iraisais sur les images et je téléchargerais élément par élément
const items = [ { id: 1, images: [ 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', ] }, { id: 2, images: [ 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', ] }, { id: 3, images: [ 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', 'https://cdn.vox-cdn.com/thumbor/XKPu8Ylce2Cq6yi_pgyLyw80vb4=/0x0:1920x1080/1200x800/filters:focal(807x387:1113x693)/cdn.vox-cdn.com/uploads/chorus_image/image/63380914/PIA16695_large.0.jpg', ] } ];
Je pense que je préfère toujours l'implémentation de Jonas pour être concise, mais j'en ajouterai une autre à l'anneau. Quelques fonctionnalités:
Promise.all
soit résolu. async function fakeRequest({ value, time = 100, shouldFail = false }) { return new Promise((resolve, reject) => { setTimeout(() => { if (shouldFail) { reject("Failure: " + value); } else { resolve("Success: " + value); } }, time); }); } test("basic 'working' prototype", async () => { const values = [1, 2, 3, 4, 5, 6]; const results = await parallelMap(values, value => { return fakeRequest({ value, time: 100, shouldFail: value % 2 === 0 }); }); expect(results).toEqual([ "Success: 1", "Failure: 2", "Success: 3", "Failure: 4", "Success: 5", "Failure: 6" ]); }, 350); // each task takes ~100ms to complete, 6 tasks, two at a time = ~300 ms
Utilisation :
function parallelMap(values, workFn, maxConcurrency = 2) { const length = values.length; const results = Array.from({ length }); let pos = 0; let completed = 0; return new Promise(resolve => { function work() { if (completed === length) { return resolve(results); } if (pos >= length) { return; } const workIndex = pos; const item = values[workIndex]; pos = pos + 1; return workFn(item, workIndex) .then(result => { results[workIndex] = result; completed = completed + 1; work(); }) .catch(result => { results[workIndex] = result; completed = completed + 1; work(); }); } for (let i = 0; i < maxConcurrency; i++) { work(); } }); }
Voir le codesandbox pour une suite de tests complète.