Promise.all rejette au premier échec - si un CAPTCHA sur cinquante échoue, vous perdez tous les résultats en attente. Promise.allSettled attend que chaque promesse se réalise, vous donnant chaque succès et chaque échec. Pour la résolution de CAPTCHA par lots, c'est le bon outil.
Promise.all contre Promise.allSettled
// Promise.all — REJECTS if ANY task fails
const results = await Promise.all(tasks.map(solve)); // Throws on first error
// Promise.allSettled — RESOLVES always, with status for each
const results = await Promise.allSettled(tasks.map(solve));
// [{status: "fulfilled", value: "..."}, {status: "rejected", reason: Error}]
| Méthode | Au premier échec | Retours | Idéal pour |
|---|---|---|---|
Promise.all |
Rejette immédiatement | Rien (lance) | Opérations tout ou rien |
Promise.allSettled |
Continue | Chaque résultat | Résolution de CAPTCHA par lots |
Implémentation de base
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveCaptcha(sitekey, pageurl) {
// Submit
const submitResp = await axios.post(
"https://ocr.captchaai.com/in.php",
null,
{
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
json: 1,
},
}
);
if (submitResp.data.status !== 1) {
throw new Error(submitResp.data.request);
}
const captchaId = submitResp.data.request;
// Poll
for (let i = 0; i < 60; i++) {
await sleep(5000);
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (result.data.status === 1) return result.data.request;
if (result.data.request !== "CAPCHA_NOT_READY") {
throw new Error(result.data.request);
}
}
throw new Error("TIMEOUT");
}
async function batchSolve(tasks) {
const promises = tasks.map((task) =>
solveCaptcha(task.sitekey, task.pageurl).then((solution) => ({
...task,
solution,
}))
);
const results = await Promise.allSettled(promises);
const solved = [];
const failed = [];
for (let i = 0; i < results.length; i++) {
if (results[i].status === "fulfilled") {
solved.push(results[i].value);
} else {
failed.push({
task: tasks[i],
error: results[i].reason.message,
});
}
}
return { solved, failed };
}
// Usage
(async () => {
const tasks = [
{
sitekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
pageurl: "https://example.com/page/1",
},
{
sitekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
pageurl: "https://example.com/page/2",
},
{
sitekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
pageurl: "https://example.com/page/3",
},
];
const { solved, failed } = await batchSolve(tasks);
console.log(`Solved: ${solved.length}, Failed: ${failed.length}`);
for (const s of solved) {
console.log(` ✓ ${s.pageurl}: ${s.solution.substring(0, 30)}...`);
}
for (const f of failed) {
console.log(` ✗ ${f.task.pageurl}: ${f.error}`);
}
})();
Contrôler la concurrence
L’envoi simultané de 1 000 CAPTCHA surcharge les connexions. Utilisez un limiteur de concurrence :
async function batchSolveWithLimit(tasks, concurrency = 10) {
const results = [];
let index = 0;
async function worker() {
while (index < tasks.length) {
const i = index++;
const task = tasks[i];
try {
const solution = await solveCaptcha(task.sitekey, task.pageurl);
results[i] = { status: "fulfilled", value: { ...task, solution } };
} catch (err) {
results[i] = { status: "rejected", reason: err };
}
}
}
// Launch concurrent workers
const workers = Array.from({ length: concurrency }, () => worker());
await Promise.allSettled(workers);
const solved = results
.filter((r) => r.status === "fulfilled")
.map((r) => r.value);
const failed = results
.filter((r) => r.status === "rejected")
.map((r, i) => ({ task: tasks[i], error: r.reason.message }));
return { solved, failed };
}
// Solve 100 CAPTCHAs, 10 at a time
const { solved, failed } = await batchSolveWithLimit(tasks, 10);
Réessayer les tâches ayant échoué
Soumettez à nouveau automatiquement les CAPTCHA ayant échoué :
async function batchSolveWithRetry(tasks, maxRetries = 2, concurrency = 10) {
let currentTasks = [...tasks];
let allSolved = [];
for (let attempt = 0; attempt <= maxRetries; attempt++) {
if (currentTasks.length === 0) break;
console.log(
`Attempt ${attempt + 1}: solving ${currentTasks.length} tasks...`
);
const { solved, failed } = await batchSolveWithLimit(
currentTasks,
concurrency
);
allSolved = [...allSolved, ...solved];
// Only retry transient errors
const retryable = failed.filter(
(f) =>
f.error === "TIMEOUT" ||
f.error === "ERROR_NO_SLOT_AVAILABLE" ||
f.error === "ERROR_TOO_MUCH_REQUESTS"
);
currentTasks = retryable.map((f) => f.task);
if (retryable.length > 0) {
console.log(` Retrying ${retryable.length} failed tasks...`);
}
}
const finalFailed = currentTasks; // Anything left after all retries
return { solved: allSolved, failed: finalFailed };
}
Suivi des progrès
Signalez la progression en temps réel pendant les opérations par lots :
async function batchSolveWithProgress(tasks, concurrency = 10) {
let completed = 0;
let succeeded = 0;
let failed = 0;
const wrapped = tasks.map((task) =>
solveCaptcha(task.sitekey, task.pageurl)
.then((solution) => {
succeeded++;
completed++;
process.stdout.write(
`\rProgress: ${completed}/${tasks.length} (${succeeded} ok, ${failed} err)`
);
return { ...task, solution };
})
.catch((err) => {
failed++;
completed++;
process.stdout.write(
`\rProgress: ${completed}/${tasks.length} (${succeeded} ok, ${failed} err)`
);
throw err;
})
);
const results = await Promise.allSettled(wrapped);
console.log("\nDone.");
return results;
}
Traitement structuré des résultats
Analysez les résultats de Promise.allSettled en catégories exploitables :
function categorizeResults(settled, originalTasks) {
const categories = {
solved: [],
transientErrors: [],
permanentErrors: [],
};
const TRANSIENT = new Set([
"TIMEOUT",
"ERROR_NO_SLOT_AVAILABLE",
"ERROR_TOO_MUCH_REQUESTS",
]);
for (let i = 0; i < settled.length; i++) {
const r = settled[i];
if (r.status === "fulfilled") {
categories.solved.push(r.value);
} else {
const error = r.reason.message;
const entry = { task: originalTasks[i], error };
if (TRANSIENT.has(error)) {
categories.transientErrors.push(entry);
} else {
categories.permanentErrors.push(entry);
}
}
}
return categories;
}
Dépannage
| Problème | Parce que | Corriger |
|---|---|---|
| Expiration de toutes les tâches | Trop de requêtes simultanées écrasant CaptchaAI ou proxy | Réduire la simultanéité à 5-10 |
ERR_SOCKET_EXHAUSTION |
Trop de connexions HTTP simultanées | Utilisez http.Agent avec la limite maxSockets |
| Tableau de résultats dans le désordre | L'ordre d'achèvement asynchrone diffère de la soumission | Utiliser le stockage des résultats basé sur un index (illustré ci-dessus) |
| Mémoire croissante lors de gros lots | Garder toutes les promesses en mémoire | Traiter en morceaux de 100 à 500 |
FAQ
Quel niveau de simultanéité dois-je utiliser ?
Commencez avec 10 tâches simultanées. Augmentez jusqu'à ce que vous voyiez des rendements décroissants ou des taux d'erreur augmenter. La plupart des configurations fonctionnent bien avec 10 à 50 résolutions simultanées.
Dois-je utiliser Promise.allSettled pour les petits lots (<5) ?
Pour les petits lots, la différence entre Promise.all et Promise.allSettled est négligeable. Utilisez quand même Promise.allSettled : c'est une bonne habitude qui évite la perte de données due à des pannes partielles.
Comment cela se compare-t-il aux générateurs ou aux flux asynchrones ?
Promise.allSettled est orienté par lots : vous soumettez tout et attendez. Pour les flux de tâches continus (par exemple, gratter des pages sans fin), utilisez un générateur asynchrone ou un pool de tâches avec une file d'attente.
Prochaines étapes
Résolvez les CAPTCHA en parallèle -récupérez votre clé API CaptchaAIet lancer des opérations par lots.
Guides associés :
- Résolution parallèle de CAPTCHA
- Résolution de CAPTCHA par lots : tâches multiples
- Résoudre 10 000 tâches par heure