Không thể hủy bỏ Promise trong JavaScript? Thực ra có cách đặc biệt để can thiệp luồng bất đồng bộ
JavaScript không có cơ chế hủy promise trực tiếp, nhưng một kỹ thuật dùng "promise không bao giờ resolve" có thể giúp tạm dừng hàm bất đồng bộ mà không cần ném lỗi hay dùng generator. Phương pháp này ứng dụng trong SDK Inngest để triển khai workflow chạy từng bước, lưu trạng thái và tiếp tục lại sau, rất hữu ích trong môi trường serverless giới hạn thời gian chạy.

Không thể hủy bỏ Promise trong JavaScript? Thực ra có cách đặc biệt để can thiệp luồng bất đồng bộ
JavaScript vốn không hỗ trợ hủy promise — không có .cancel() hay tích hợp với AbortController. Điều này xuất phát từ việc dừng một đoạn code đang chạy giữa chừng có thể làm tài nguyên bị treo hoặc dữ liệu chưa được ghi đầy đủ. Tuy nhiên, một kỹ thuật kỳ lạ nhưng hữu dụng chính là trả về một promise không bao giờ resolve, khiến hàm async bị "treo" tạm thời và runtime có thể thoải mái thu dọn bộ nhớ khi không còn tham chiếu đến nó nữa.
Tại sao cần "dừng" hàm bất đồng bộ?
Trong các ứng dụng thực tế, đặc biệt là với serverless hoặc các môi trường có giới hạn thời gian chạy, workflow có thể bao gồm nhiều bước mất nhiều thời gian. Bạn cần:
- Dừng hàm async ở đúng thời điểm
- Lưu trạng thái bước hiện tại
- Tiếp tục chạy hàm lần nữa từ bước đã dừng mà không làm code của người dùng bị ảnh hưởng
Cách truyền thống dùng throw để ngắt luồng dễ bị bỏ qua trong try/catch, gây sai lệch logic. Generator có thể điều khiển luồng rõ ràng nhờ .next(), nhưng đổi lại là cú pháp phức tạp và không hỗ trợ đồng thời tốt như async/await.
Kỹ thuật promise không bao giờ resolve
Thay vì ném lỗi để ngắt, ta trả về một Promise mà callback hoàn toàn rỗng, khiến promise đó không bao giờ kết thúc. Khi await trên promise đó, hàm async sẽ "đóng băng" đợi vô thời hạn.
async function interrupt() {
return new Promise(() => {});
}
async function main() {
console.log("Before interrupt");
await interrupt();
console.log("After interrupt"); // Dòng này không bao giờ thực thi
}
Node.js sẽ thoát nếu không còn handle nào giữ vòng lặp sự kiện, bởi vì một promise chưa resolve không giữ luồng sự kiện hoạt động. Khi thêm một timer như setTimeout, chương trình sẽ duy trì chạy cho tới khi timer hết hạn.
Ứng dụng trong workflow: chạy theo từng bước và nhớ kết quả
Giả sử có hàm workflow:
async function myWorkflow(step) {
const data = await step.run("fetch", () => [1, 2, 3]);
const processed = await step.run("process", () => data.map(n => n * 2));
console.log("Complete", processed);
}
Runtime sẽ gọi hàm này nhiều lần, mỗi lần thực thi một bước mới và lưu lại kết quả để lần sau "memoize" mà không chạy lại.
Để thực hiện, runtime dùng:
step.runtrả về kết quả đã lưu nếu bước đã chạy- Hoặc trả về promise treo để dừng hàm chờ bước mới xử lý
async function execute(fn, stepState) {
let newStep = null;
fn({
run: async (id, callback) => {
if (stepState.has(id)) return stepState.get(id);
newStep = {id, callback};
return new Promise(() => {}); // Promise không resolve để tạm dừng
},
});
await new Promise(r => setTimeout(r, 0)); // Cho phép các microtask hoàn thành
if (newStep) {
const result = await newStep.callback();
stepState.set(newStep.id, result);
return false; // Chưa xong, cần chạy tiếp
}
return true; // Đã xong hết các bước
}
Nhờ vậy, workflow có thể:
- Chạy lại từ đầu mỗi lần gọi nhưng bỏ qua các bước đã hoàn thành
- Giữ trạng thái bộ nhớ thông qua
stepState(ở thực tế lưu xuống database để bật lại sau khi instance serverless tắt) - Dừng hàm async mà không ném lỗi, không dùng generator, và vẫn giữ được cú pháp async/await quen thuộc
Giải pháp này có rò rỉ bộ nhớ không?
Những promise treo suốt thời gian dài có thể gây lo ngại về rò rỉ bộ nhớ. Tuy nhiên, JavaScript có garbage collector mạnh mẽ:
- Nếu hàm async không còn tham chiếu (đóng stack không thể tiếp tục) thì toàn bộ promise treo cũng được thu gom
- Ứng dụng thực tế của Inngest SDK sử dụng
FinalizationRegistryđể theo dõi và xác nhận promise bị thu gom đúng lúc
Điều quan trọng là không giữ các tham chiếu không cần thiết đến promise hoặc state cũ để tránh rò rỉ bộ nhớ.
Kết luận
Kỹ thuật dùng promise không bao giờ resolve nghe có vẻ kỳ lạ nhưng là một công cụ kiểm soát luồng bất đồng bộ tinh tế. Nó cho phép:
- Ngắt hàm async/await mà không cần generator hay ném lỗi
- Viết code workflow phức tạp trong môi trường bất đồng bộ như serverless
- Quản lý trạng thái và thực thi từng bước dễ dàng
Cách làm này đã được chứng minh trong thực tế bởi Inngest SDK — một ví dụ về sự sáng tạo trong lập trình JavaScript hiện đại.
Điều khiển luồng async bằng promise không resolve
Bài viết liên quan

Phần mềm
Anthropic ra mắt Claude Opus 4.7: Nâng cấp mạnh mẽ cho lập trình nhưng vẫn thua Mythos Preview
16 tháng 4, 2026

Công nghệ
Qwen3.6-35B-A3B: Quyền năng Lập trình Agentic, Nay Đã Mở Cửa Cho Tất Cả
16 tháng 4, 2026

Công nghệ
Spotify thắng kiện 322 triệu USD từ nhóm pirate Anna's Archive nhưng đối mặt với bài toán thu hồi
16 tháng 4, 2026
