hocvietcode.com
  • Trang chủ
  • Học lập trình
    • Lập trình C/C++
    • Cấu trúc dữ liệu và giải thuật
    • Lập trình HTML
    • Lập trình Javascript
      • Javascript cơ bản
      • ReactJS framework
      • AngularJS framework
      • Typescript cơ bản
      • Angular
      • NodeJS
    • Lập trình Mobile
      • Lập Trình Dart Cơ Bản
        • Dart Flutter Framework
    • Cơ sở dữ liệu
      • MySQL – MariaDB
      • Micrsoft SQL Server
      • Extensible Markup Language (XML)
      • JSON
      • MongoDB
    • Lập trình PHP
      • Lập trình PHP cơ bản
      • Laravel Framework
    • Lập trình Java
      • Java Cơ bản
    • Lập trình C#
      • Lập Trình C# Cơ Bản
      • ASP.NET Core MVC
    • Machine Learning
    • Git và Github
  • WORDPRESS
    • WordPress cơ bản
    • WordPress nâng cao
    • Chia sẻ WordPress
  • Kiến thức hệ thống
    • Microsoft Azure
    • Docker
    • Linux
  • Chia sẻ IT
    • Tin học văn phòng
      • Microsoft Word
      • Microsoft Excel
    • Marketing
      • Google Adwords
      • Facebook Ads
      • Kiến thức khác
    • Chia sẻ phần mềm
    • Review công nghệ
    • Công cụ – tiện ích
      • Kiểm tra bàn phím online
      • Kiểm tra webcam online
Đăng nhập
  • Đăng nhập / Đăng ký

Please enter key search to display results.

Home
  • NodeJS
Đồng bộ, bất đồng bộ, Promise, async/await và Event Loop

Đồng bộ, bất đồng bộ, Promise, async/await và Event Loop

  • 24-07-2026
  • Toanngo92
  • 0 Comments

Khả năng xử lý bất đồng bộ là lý do Node.js phục vụ được nhiều kết nối mà không cần tạo một luồng JavaScript riêng cho từng yêu cầu. Bài này phân biệt code đồng bộ và bất đồng bộ, lần lượt thực hành callback, Promise, async/await, rồi nối các khái niệm đó với Event Loop.

Minh họa lập trình đồng bộ và bất đồng bộ trong Node.js
Session 7: Asynchronous and Synchronous Programming.

Mục lục

  • 1. Đồng bộ và bất đồng bộ là gì?
    • Khi nào dùng mỗi loại?
  • 2. Ví dụ đọc tệp đồng bộ
  • 3. Callback trong Node.js
    • Tránh callback hell
  • 4. Promise và ba trạng thái
  • 5. async và await
    • Tuần tự và song song có kiểm soát
  • 6. Event Loop trong Node.js
    • Các giai đoạn chính
  • 7. setTimeout, setImmediate và hàng đợi vi tác vụ
  • 8. Timer không phải đồng hồ chính xác
  • 9. Lỗi thường gặp
  • 10. Bài tập thực hành
  • Tổng kết

1. Đồng bộ và bất đồng bộ là gì?

Trong mô hình đồng bộ, mỗi câu lệnh phải hoàn tất trước khi câu lệnh kế tiếp chạy. Nếu một thao tác đọc tệp mất hai giây, JavaScript sẽ chờ đủ hai giây. Cách này dễ theo dõi nhưng có thể chặn toàn bộ tiến trình.

Trong mô hình bất đồng bộ, chương trình khởi tạo thao tác I/O rồi tiếp tục công việc khác. Khi I/O hoàn tất, kết quả được đưa trở lại hàng đợi để callback hoặc phần tiếp theo của Promise được chạy. Điều này không có nghĩa mọi đoạn JavaScript chạy song song; một callback JavaScript dài vẫn có thể chặn Event Loop.

So sánh lập trình bất đồng bộ và đồng bộ
Đồng bộ thực thi tuần tự; bất đồng bộ phù hợp với các tác vụ I/O cần chờ.

Khi nào dùng mỗi loại?

  • Dùng API bất đồng bộ cho server, đọc tệp, truy vấn cơ sở dữ liệu, gọi API và phần lớn tác vụ I/O.
  • API đồng bộ phù hợp với script ngắn, bước khởi tạo chạy một lần hoặc công cụ dòng lệnh khi việc chặn tiến trình có chủ đích.
  • Công việc nặng CPU như nén ảnh lớn hay tính toán kéo dài không tự trở nên bất đồng bộ chỉ vì đặt trong Promise; nên cân nhắc Worker Threads hoặc tiến trình riêng.

2. Ví dụ đọc tệp đồng bộ

const { readFileSync } = require('node:fs');

console.log('Bắt đầu');
const content = readFileSync('./message.txt', 'utf8');
console.log(content);
console.log('Kết thúc');

Thứ tự luôn là “Bắt đầu”, nội dung tệp, rồi “Kết thúc”. Trong lúc readFileSync() làm việc, Event Loop không thể xử lý yêu cầu khác.

3. Callback trong Node.js

Callback là hàm được truyền cho một hàm khác và được gọi khi thao tác hoàn tất. Quy ước phổ biến của Node.js là (error, result): đối số đầu tiên chứa lỗi, đối số tiếp theo chứa kết quả.

const { readFile } = require('node:fs');

console.log('Bắt đầu');

readFile('./message.txt', 'utf8', (error, content) => {
  if (error) {
    console.error('Không đọc được tệp:', error.message);
    return;
  }
  console.log(content);
});

console.log('Đã gửi yêu cầu đọc tệp');

Dòng cuối thường xuất hiện trước nội dung tệp, vì callback chỉ được xếp lịch sau khi thao tác đọc hoàn tất.

Luồng thực thi callback đọc tệp trong Node.js
Luồng chính tiếp tục chạy trong lúc thao tác đọc tệp được xử lý.

Tránh callback hell

Khi nhiều tác vụ phụ thuộc lồng vào nhau, callback có thể tạo thành cấu trúc khó đọc. Hãy tách callback thành hàm có tên, trả lỗi sớm, hoặc dùng Promise và async/await.

4. Promise và ba trạng thái

Promise đại diện cho kết quả sẽ có trong tương lai. Nó có ba trạng thái: pending, fulfilled và rejected. Promise chỉ chuyển khỏi trạng thái pending một lần.

const { readFile } = require('node:fs');

function readText(path) {
  return new Promise((resolve, reject) => {
    readFile(path, 'utf8', (error, content) => {
      if (error) return reject(error);
      resolve(content);
    });
  });
}

readText('./message.txt')
  .then(content => console.log(content))
  .catch(error => console.error(error.message))
  .finally(() => console.log('Đã xử lý xong'));

Trong code hiện đại, Node.js đã cung cấp sẵn node:fs/promises, vì vậy không cần tự bọc readFile trong Promise cho trường hợp này.

5. async và await

Từ khóa async khiến hàm luôn trả về Promise. await tạm dừng riêng hàm async hiện tại cho đến khi Promise ổn định; nó không làm cả tiến trình Node.js dừng lại.

const { readFile } = require('node:fs/promises');

async function showMessage() {
  try {
    const content = await readFile('./message.txt', 'utf8');
    console.log(content);
  } catch (error) {
    console.error('Đọc tệp thất bại:', error.message);
  }
}

showMessage();

Tuần tự và song song có kiểm soát

Hai lệnh await liên tiếp chạy tuần tự. Nếu hai tác vụ độc lập, khởi tạo chúng cùng lúc rồi dùng Promise.all():

const { readFile } = require('node:fs/promises');

async function loadFiles() {
  const [config, users] = await Promise.all([
    readFile('./config.json', 'utf8'),
    readFile('./users.json', 'utf8')
  ]);

  return {
    config: JSON.parse(config),
    users: JSON.parse(users)
  };
}

Promise.all() sẽ reject ngay khi một Promise reject. Nếu cần thu thập cả kết quả thành công lẫn thất bại, dùng Promise.allSettled().

6. Event Loop trong Node.js

Event Loop phối hợp việc chạy callback JavaScript. Các thao tác I/O có thể được hệ điều hành hoặc thread pool của libuv xử lý. Khi kết quả sẵn sàng, callback tương ứng được đưa vào giai đoạn phù hợp để JavaScript thực thi.

Sơ đồ Event Loop, hàng đợi và thread pool trong Node.js
Event Loop nhận callback sẵn sàng từ I/O và thread pool để chạy trên luồng JavaScript.

Các giai đoạn chính

  • Timers: chạy callback của timer đã đến ngưỡng thời gian.
  • Pending callbacks: chạy một số callback hệ thống được hoãn từ vòng trước.
  • Poll: nhận sự kiện I/O mới và chạy callback I/O khi thích hợp.
  • Check: chạy callback được lên lịch bởi setImmediate().
  • Close callbacks: xử lý các sự kiện đóng, chẳng hạn socket phát close.
Các giai đoạn của Event Loop trong Node.js
Các giai đoạn Event Loop được lặp lại khi tiến trình còn công việc cần xử lý.

7. setTimeout, setImmediate và hàng đợi vi tác vụ

setTimeout(fn, 0) không cam kết chạy ngay lập tức; nó chỉ đặt ngưỡng sớm nhất. setImmediate() lên lịch callback cho giai đoạn check, thường hữu ích sau I/O. Promise handlers và queueMicrotask() nằm trong hàng đợi vi tác vụ, được xử lý trước khi chuyển sang giai đoạn Event Loop kế tiếp. process.nextTick() còn có mức ưu tiên riêng; lạm dụng nó có thể làm I/O bị “đói”.

console.log('1. đồng bộ');

setTimeout(() => console.log('4. timer'), 0);
setImmediate(() => console.log('5. immediate'));

Promise.resolve().then(() => console.log('3. Promise'));
process.nextTick(() => console.log('2. nextTick'));

Trong ví dụ đơn giản này, phần đồng bộ chạy trước, sau đó đến nextTick và Promise. Thứ tự tương đối giữa timer và immediate có thể phụ thuộc ngữ cảnh, đặc biệt là đang ở top-level hay bên trong callback I/O.

8. Timer không phải đồng hồ chính xác

console.log('Timer bắt đầu');

setTimeout(() => {
  console.log('Chạy sau ít nhất 1 giây');
}, 1000);

console.log('Timer đã được lên lịch');

Nếu Event Loop đang bận ở thời điểm một giây, callback phải chờ. Tham số delay là thời gian tối thiểu, không phải lời hứa về thời điểm chính xác.

9. Lỗi thường gặp

  • Quên await nên nhận Promise thay vì dữ liệu thật.
  • Dùng await ngoài hàm async trong tệp CommonJS.
  • Không bắt Promise rejection, làm lỗi lan đến cấp tiến trình.
  • Dùng hàm *Sync trong route Express và chặn mọi request khác.
  • Dùng forEach(async () => ...) rồi tưởng rằng vòng lặp sẽ đợi. Hãy dùng for...of cho tuần tự hoặc Promise.all(array.map(...)) cho đồng thời.

10. Bài tập thực hành

  1. Đọc cùng một tệp bằng readFileSync, callback và node:fs/promises; so sánh thứ tự log.
  2. Đọc ba tệp độc lập bằng Promise.all() và xử lý trường hợp một tệp không tồn tại.
  3. Dùng setTimeout để in hai thông báo sau 2 và 4 giây.
  4. Thêm một vòng lặp CPU nặng rồi quan sát timer bị trễ như thế nào.

Tổng kết

API đồng bộ đơn giản nhưng có thể chặn Event Loop. Callback là nền tảng của nhiều API Node.js; Promise và async/await giúp tổ chức luồng bất đồng bộ dễ đọc hơn. Event Loop không biến JavaScript thành đa luồng, mà điều phối callback sẵn sàng để một luồng JavaScript có thể phục vụ hiệu quả nhiều tác vụ I/O.

Bài viết liên quan:

Hoàn thiện ứng dụng quản lý phim, xác thực và triển khai Render
Xây dựng ứng dụng quản lý phim với Express, EJS và MongoDB
Xây dựng website với Express, EJS và MongoDB
Kết nối MongoDB và xây dựng CRUD API
Xây dựng RESTful API với Express và HTTP trong nodejs
Express.js, Routing, GET/POST và Middleware
Query String, gửi Email, Upload File và Local Modules trong Node.js
Built-in Modules trong Node.js: HTTP Server, URL và EventEmitter
Modules và Packages trong Node.js: CommonJS, ES Modules, npm và File System
Node.js Essentials: kiến trúc, Event Loop, REPL, npm và Global Objects
Node.js là gì? Kiến trúc client-server và cách cài đặt Node.js
Tìm Hiểu Express.js và Lập Trình Đồng Bộ – Bất Đồng Bộ Trong Node.js

THÊM BÌNH LUẬN Cancel reply

Dịch vụ thiết kế Wesbite

NỘI DUNG MỚI CẬP NHẬT

Hoàn thiện ứng dụng quản lý phim, xác thực và triển khai Render

Xây dựng ứng dụng quản lý phim với Express, EJS và MongoDB

Xây dựng website với Express, EJS và MongoDB

Đồng bộ, bất đồng bộ, Promise, async/await và Event Loop

Express.js, Routing, GET/POST và Middleware

Giới thiệu

hocvietcode.com là website chia sẻ và cập nhật tin tức công nghệ, chia sẻ kiến thức, kỹ năng. Chúng tôi rất cảm ơn và mong muốn nhận được nhiều phản hồi để có thể phục vụ quý bạn đọc tốt hơn !

Liên hệ quảng cáo: [email protected]

Kết nối với HỌC VIẾT CODE

© hocvietcode.com - Tech888 Co .Ltd since 2019

Đăng nhập

Trở thành một phần của cộng đồng của chúng tôi!
Registration complete. Please check your email.
Đăng nhập bằng google
Đăng kýBạn quên mật khẩu?

Create an account

Welcome! Register for an account
The user name or email address is not correct.
Registration confirmation will be emailed to you.
Log in Lost your password?

Reset password

Recover your password
Password reset email has been sent.
The email could not be sent. Possible reason: your host may have disabled the mail function.
A password will be e-mailed to you.
Log in Register
×