Promise.all từ chối ở lần thất bại đầu tiên - nếu một trong số 50 CAPTCHA không thành công, bạn sẽ mất tất cả các kết quả đang chờ xử lý. Promise.allSettled đợi mọi lời hứa hoàn thành, mang lại cho bạn mọi thành công và mọi thất bại. Để giải quyết CAPTCHA hàng loạt, đây là công cụ phù hợp.
Promise.all vs 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}]
| phương pháp | Trong lần thất bại đầu tiên | Trả lại | Tốt nhất cho |
|---|---|---|---|
Promise.all |
Từ chối ngay lập tức | Không có gì (ném) | Hoạt động tất cả hoặc không có gì |
Promise.allSettled |
Tiếp tục | Mọi kết quả | Giải CAPTCHA hàng loạt |
Triển khai cốt lõi
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}`);
}
})();
Kiểm soát đồng thời
Gửi 1.000 CAPTCHA đồng thời làm quá tải kết nối. Sử dụng bộ giới hạn đồng thời:
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);
Thử lại các tác vụ thất bại
Tự động gửi lại CAPTCHA không thành công:
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 };
}
Theo dõi tiến độ
Báo cáo tiến độ thời gian thực trong các hoạt động hàng loạt:
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;
}
Xử lý kết quả có cấu trúc
Phân tích kết quả Promise.allSettled thành các danh mục có thể thực hiện được:
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;
}
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
| Hết thời gian chờ tất cả nhiệm vụ | Quá nhiều yêu cầu đồng thời áp đảo CaptchaAI hoặc proxy | Giảm đồng thời xuống 5-10 |
ERR_SOCKET_EXHAUSTION |
Quá nhiều kết nối HTTP đồng thời | Sử dụng http.Agent với giới hạn maxSockets |
| Mảng kết quả không đúng thứ tự | Thứ tự hoàn thành không đồng bộ khác với thứ tự gửi | Sử dụng lưu trữ kết quả dựa trên chỉ mục (hiển thị ở trên) |
| Bộ nhớ tăng lên theo đợt lớn | Giữ tất cả lời hứa trong bộ nhớ | Xử lý theo khối 100-500 |
Câu hỏi thường gặp
Tôi nên sử dụng mức đồng thời nào?
Bắt đầu với 10 nhiệm vụ đồng thời. Tăng cho đến khi bạn thấy lợi nhuận giảm dần hoặc tỷ lệ lỗi tăng lên. Hầu hết các thiết lập đều hoạt động tốt ở mức 10–50 lần giải quyết đồng thời.
Tôi có nên sử dụng Promise.allSettled cho các lô nhỏ (< 5) không?
Đối với các lô nhỏ, sự khác biệt giữa Promise.all và Promise.allSettled là không đáng kể. Vẫn sử dụng Promise.allSettled - đó là một thói quen tốt giúp ngăn ngừa mất dữ liệu do lỗi một phần.
Điều này so sánh với các trình tạo hoặc luồng không đồng bộ như thế nào?
Promise.allSettled được định hướng theo đợt - bạn gửi mọi thứ và chờ đợi. Đối với các luồng tác vụ liên tục (ví dụ: quét các trang không ngừng), hãy sử dụng trình tạo không đồng bộ hoặc nhóm công nhân có hàng đợi.
Các bước tiếp theo
Giải quyết song song CAPTCHA —lấy khóa API CaptchaAI của bạnvà khởi động các hoạt động hàng loạt.
Hướng dẫn liên quan: