Người mới học JavaScript thử tìm hiểu bản chất của “Bất đồng bộ (Asynchronous)” (JS初心者が『非同期』を理解してみた)
Giải thích trực quan sự khác biệt giữa Xử lý đồng bộ (同期処理) và Xử lý bất đồng bộ (非同期処理) trong JavaScript.

✍️ Tác giả gốc:
koxrtx (株式会社シンシア)trên diễn đàn công nghệ Qiita Nhật Bản
🔗 Tiêu đề gốc có Furigana: JS初心者が『非同期』を理解してみた
🌐 Liên kết bài gốc: JS初心者が『非同期』を理解してみた
📌 Điểm nhấn cốt lõi (Key Takeaways)
- Giải thích trực quan sự khác biệt giữa Xử lý đồng bộ (同期処理) và Xử lý bất đồng bộ (非同期処理) trong JavaScript.
- Minh họa bằng ví dụ thực tế với fetch, setTimeout (5 giây và 0 giây), Promise (3 trạng thái: pending, fulfilled, rejected) và async/await.
- Chỉ ra nguyên nhân gây đơ màn hình (UI freeze) khi dùng đồng bộ cho tác vụ nặng và lỗi undefined kinh điển khi chưa đợi kết quả bất đồng bộ.
1. Mở đầu (1. はじめに)
Trước đây tôi chỉ hiểu lơ mơ rằng bất đồng bộ là thứ gì đó chạy ngầm phía sau để xử lý êm xuôi các tác vụ nặng, chứ chưa thực sự hiểu rõ bất đồng bộ đang làm gì. Dù có được hỏi “Tại sao phải viết bằng bất đồng bộ?”, tôi cũng chỉ trả lời được đại khái là “Nếu không bất đồng bộ thì màn hình sẽ không hiển thị cho đến khi xử lý xong và bị đơ nặng”. Vì lý do tại sao bắt buộc phải dùng bất đồng bộ vẫn còn mơ hồ, nên khi thực tế viết code tôi rất hay phân vân “Chỗ này dùng bất đồng bộ có ổn không nhỉ?”. Vì vậy trong bài viết này, tôi đặt mục tiêu là hiểu rõ xử lý bất đồng bộ thực chất đang làm những gì.
非同期とはなんか裏で重たい処理をいい感じにしてくれてるくらいで非同期が何をしているかあまり理解できてませんでした。
2. Xử lý đồng bộ (Synchronous Processing) (2. 同期処理)
console.log('1');
heavyWork(); // これが終わるまで待つ
console.log('2'); // heavyWork が終わってから実行される
📝 Trước khi tìm hiểu xử lý bất đồng bộ, tôi sẽ giải thích về xử lý đồng bộ. Xử lý đồng bộ là: Cách thức thực thi lần lượt từng lệnh từ trên xuống dưới, và không chuyển sang dòng tiếp theo cho đến khi xử lý phía trước hoàn tất.
Chuyện gì xảy ra nếu gọi một tác vụ tốn thời gian bằng xử lý đồng bộ? Việc truyền thông lấy dữ liệu từ máy chủ (server) nhanh hay chậm còn tùy thuộc vào tình trạng máy chủ và tốc độ đường truyền nên không biết khi nào mới xong. Do đó nếu viết đồng bộ, chương trình không thể sang dòng kế tiếp cho tới khi dữ liệu trả về, mạng càng chậm thì màn hình càng bị đơ cứng lâu. Trong khoảng thời gian đó sẽ xảy ra các hiện tượng như: “Bấm nút không phản hồi”, “Không cuộn trang được”, “Hiển thị màn hình không cập nhật”, “Hiệu ứng chuyển động bị đứng”, khiến người dùng nhìn vào cảm giác như trang web đã bị treo cứng. Tài liệu chính thức (MDN) cũng nêu rõ: “Tại một thời điểm chỉ có thể làm một việc duy nhất. Vì thế, từ lúc gọi một xử lý đồng bộ có thời gian thực thi dài cho đến khi nó quay trở lại, trong suốt khoảng thời gian đó không thể làm bất cứ việc gì khác.”
同期処理で時間のかかる処理を呼び出すとどうなる? サーバーからデータを取ってくる通信は、相手の状況や回線の速さによって、いつ終わるか分かりません。なので同期処理で書くと、データが返ってくるまで次の行に進めず、通信が遅いほど長く固まります。
- Vậy khi nào thì dùng xử lý đồng bộ? Thực tế, hầu hết code chúng ta viết hằng ngày đều được viết bằng xử lý đồng bộ: Tính toán (tính tổng tiền, giá đã gồm thuế); Thao tác mảng (lọc hoặc sắp xếp danh sách bằng map, filter, sort); Xử lý chuỗi (ghép tên, định dạng ngày tháng); Rẽ nhánh điều kiện (dùng if kiểm tra đã đăng nhập chưa); Kiểm tra đầu vào / Validation (xác nhận định dạng email có hợp lệ không).
(じゃあ同期処理っていつ使うの? 実際は、普段書くコードのほとんどは同期処理で書かれてることが多いです。(計算:合計金額や税込価格を出す / 配列の操作:map、filter、sortで一覧を絞り込んだり並べ替えたりする / 文字列の加工:名前の結合、日付の整形 / 条件分岐:if でログイン済みかどうかを判定する / 入力チェック(バリデーション):メールアドレスの形式が正しいか確認する)) - Những xử lý không tốn thời gian như vậy đều được viết bằng xử lý đồng bộ. Do đó, nếu những nội dung hoàn thành chỉ trong chớp mắt mà cũng viết bất đồng bộ một cách bừa bãi thì chẳng những không nhanh hơn mà còn làm code dài dòng, phức tạp hóa và làm tăng nguy cơ phát sinh bug, nên cần phải phân định rõ ràng.
(等、時間のかからない処理は同期処理で書かれています。なので、一瞬で処理が終わる内容までむやみやたらに非同期で書くと、処理が早くなるわけではなくコードが増え複雑化し、バグの原因が増える要因にもなるので、切り分けが必要です。)
3. Bất đồng bộ là gì? (3. 非同期ってなに?)
💡 Trích dẫn: Lập trình bất đồng bộ là kỹ thuật cho phép ngay cả khi bắt đầu một tác vụ có khả năng kéo dài, chương trình không phải ngồi đợi cho đến khi tác vụ đó hoàn tất mà vẫn có thể phản hồi các sự kiện khác trong lúc tác vụ kia đang chạy. Khi tác vụ hoàn tất, chương trình sẽ hiển thị kết quả của nó. (Minh họa bằng sơ đồ: sync_vs_async_timeline.png) (Gốc: 非同期プログラミングは、長く続く可能性のあるタスクを開始しても、そのタスクが完了するまで待つのではなく、そのタスクの実行中も他のイベントに応答できるようにする技術です。タスクが完了すると、プログラムはその結果を表示します。(図にするとこんな感じ: sync_vs_async_timeline.png))
console.log('1');
fetch('/api/data').then(() => {
console.log('3:データが届いた'); // 通信が終わったら実行される
});
console.log('2'); // 通信の完了を待たずに、すぐ実行される
// .then() の中には、通信が終わったあとにやりたい処理を書きます
📝 Đúng như trích dẫn từ MDN ở chương 2, JavaScript chỉ có thể làm 1 việc tại 1 thời điểm. Đặc điểm này được gọi là Đơn luồng (Single-threaded): luồng xử lý chỉ có duy nhất 1 sợi. 📝 Vì vậy, nếu viết các tác vụ tốn thời gian như giao tiếp với server bằng xử lý đồng bộ thì phải đợi đến khi xử lý xong, trong lúc đó không thể làm bất kỳ việc gì khác (như phản hồi nút bấm hay cập nhật màn hình) khiến màn hình bị đơ. Do đó, sau khi phát lệnh xong thì không chờ kết quả mà đi tiếp sang xử lý kế tiếp, rồi nhận thông báo hoặc kết quả vào đúng thời điểm xử lý kia hoàn tất — đó chính là xử lý bất đồng bộ. 📝 Kết quả thực thi sẽ theo thứ tự 1 → 2 → 3; trong lúc chờ truyền thông thì JavaScript vẫn không dừng lại nên màn hình không bị đơ.
4. Thử chạy thực tế (4. 実際に動かしてみた)
console.log('1');
setTimeout(() => {
console.log('2');
}, 5 * 1000); // 5秒後に実行
console.log('3');
📝 Ở chương 3 tôi đã đưa ra ví dụ về giao tiếp mạng, nhưng giao tiếp mạng thì không thử được nếu không có server. Vì thế, tôi sẽ dùng hàm setTimeout có thể chạy thử ngay trên trình duyệt. setTimeout: Hàm thực thi xử lý sau khi thời gian chỉ định đã trôi qua (Ảnh quay màn hình: số 2 đã xuất hiện cuối cùng). 📝 Tại sao lại như vậy: 1. console.log(‘1’) được thực thi → 2. Nhờ trình duyệt thực hiện console.log(‘2’) sau 5 giây bằng setTimeout → 3. Không đợi 5 giây mà chuyển ngay sang console.log(‘3’) tiếp theo → 4. Khi đủ 5 giây, console.log(‘2’) mới được thực thi. Nhân tiện, phần chạy ngầm phía sau trong xử lý bất đồng bộ chính là trình duyệt (Web APIs), vì khó hình dung nên tôi đã vẽ thành sơ đồ (async_setTimeout_flow.png).
console.log('1');
setTimeout(() => {
console.log('2');
}, 0); // 0秒後に実行
console.log('3');
// --- バグの一例 ---
let data;
fetch('/api/data')
.then((res) => res.json())
.then((json) => {
data = json; // 通信が終わったら、ここで値が入る
});
console.log(data); // 通信を待たずに先に実行されるのでundefined
📝 Nếu đặt thành 0 giây thì sao? Nhân tiện, nếu đặt thời gian chờ là 0 giây thì chuyện gì sẽ xảy ra? Dù có đặt 0 giây thì số 2 vẫn được xử lý cuối cùng. Tác vụ truyền vào setTimeout dù thời gian là 0 giây vẫn được xử lý như một tác vụ bất đồng bộ. Vì vậy, phải sau khi toàn bộ các xử lý đồng bộ đang chạy (cho đến hết console.log(‘3’)) kết thúc hoàn toàn thì nó mới được thực thi. 📝 Vì vậy hãy cẩn thận: nếu viết bất đồng bộ mà dùng giá trị ngay bên ngoài, sẽ phát sinh bug dữ liệu hiển thị trống rỗng (undefined) dù đã gọi lấy dữ liệu. Cách sửa là: “Viết xử lý sử dụng kết quả vào bên trong hàm bất đồng bộ” hoặc “Dùng await để đợi cho đến khi nhận được kết quả”. Tôi sẽ giới thiệu về await ở chương tiếp theo.
5. Về Promise và async/await (5. Promise と async/await について)
| 状態 (Trạng thái) | 意味 (Ý nghĩa) |
|---|---|
| 待機(pending) | まだ結果が出ていない (Chưa có kết quả) |
| 履行(fulfilled) | 成功した → .then() が呼ばれる (Thành công → gọi .then()) |
| 拒否(rejected) | 失敗した → .catch() が呼ばれる (Thất bại → gọi .catch()) |
async function getData() {
const response = await fetch('/api/data'); // 結果が出るまでここで待つ
console.log('データが届いた');
}
// --- 0.5秒ごとにカウントを表示しながら await で5秒待つ比較実験 ---
let count = 0;
const timer = setInterval(() => {
count++;
console.log(`カウント ${count}`);
if (count >= 14) clearInterval(timer);
}, 500);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function getData() {
console.log('2:待機を開始');
await wait(5 * 1000);
console.log('4:5秒たった');
}
console.log('1');
getData();
console.log('3');
// 実行結果: 1 -> 2:待機を開始 -> 3 (5秒を待たずにすぐ表示される) -> カウント... -> 4:5秒たった
// --- 同期処理で書いた場合 ---
function waitSync(ms) {
const end = Date.now() + ms;
while (Date.now() < end) {}
}
function getDataSync() {
console.log('2:待機を開始');
waitSync(5 * 1000);
console.log('4:5秒たった');
}
console.log('1');
getDataSync();
console.log('3');
// 結果は 1 → 2 →(5秒固まる)→ 4 → 3 です。
📝 async/await là gì: Nếu cứ nối chuỗi .then() liên tiếp thì code rất dễ trở nên khó đọc. Lúc đó ta dùng async/await. “Từ khóa async giúp code Promise bất đồng bộ hoạt động đơn giản hơn… Nhờ vậy, dù đang dùng hàm bất đồng bộ nhưng bạn có thể viết code thoạt nhìn giống hệt như code đồng bộ.” async: Đặt trước hàm để biến hàm đó thành hàm bất đồng bộ. await: Đặt trước Promise để đợi ngay tại chỗ đó cho đến khi có kết quả và lấy giá trị bên trong ra. Không cần dùng .then() mà vẫn đọc tuần tự từ trên xuống giống như xử lý đồng bộ. 📝 Nhưng khi nghe nói dùng await để “đợi”, tôi thắc mắc liệu nó có làm đơ màn hình giống xử lý đồng bộ không nên đã kiểm chứng thử. Tôi so sánh trường hợp vừa in bộ đếm mỗi 0.5 giây vừa đợi 5 giây bằng await với trường hợp đợi 5 giây bằng đồng bộ. Nếu trong lúc đợi bộ đếm vẫn chạy tiếp nghĩa là không bị dừng, còn bộ đếm đứng im nghĩa là bị đơ. Với await, sau dòng “2: Bắt đầu chờ”, trong suốt 5 giây chờ thì số 3 và bộ đếm vẫn tiếp tục hiển thị. Đó là vì thứ tạm dừng bởi await chỉ nằm bên trong hàm getData, còn bản thân JavaScript vẫn chạy tiếp. Ngược lại, khi viết bằng đồng bộ, kết quả là 1 → 2 → (đơ 5 giây) → 4 → 3. Sau dòng “2: Bắt đầu chờ”, suốt 5 giây không có gì hiển thị và bộ đếm cũng dừng hẳn, vì JavaScript phải dồn toàn lực vào hàm waitSync suốt 5 giây nên không làm được việc gì khác.
6. Rốt cuộc nên viết bằng Đồng bộ hay Bất đồng bộ? / 7. Tổng kết / Tài liệu tham khảo (6. 結局、同期と非同期どっちで書けばいいの? / 7. まとめ / 参考文献)
- Rốt cuộc nên viết bằng đồng bộ hay bất đồng bộ? — Các xử lý hoàn thành ngay lập tức như tính toán, thao tác mảng, rẽ nhánh điều kiện: Xử lý đồng bộ / Các xử lý có thời gian chờ cho đến khi kết quả trả về như giao tiếp mạng: Xử lý bất đồng bộ.
(結局、同期と非同期どっちで書けばいいの? — 計算・配列の操作・条件分岐など、すぐ終わる処理:同期処理 / 通信など、結果が返ってくるまで待ち時間がある処理:非同期処理) - Tổng kết — 1. JavaScript chỉ làm được 1 việc tại 1 thời điểm (Đơn luồng / Single-thread) / 2. Nếu viết các xử lý tốn thời gian như giao tiếp mạng bằng đồng bộ thì trong lúc đó màn hình sẽ bị đơ / 3. Xử lý bất đồng bộ là cơ chế giao phó tác vụ tốn thời gian cho trình duyệt thực hiện để chương trình không bị dừng lại trong lúc chờ đợi.
(まとめ — 1. JavaScriptは一度に1つのことしかできない(シングルスレッド) / 2. 通信のように時間のかかる処理を同期処理でかくと、その間は画面が固まる / 3. 非同期処理は、時間のかかる処理をブラウザに頼んでおいて、待っている間に止まらないための仕組み) - Tài liệu tham khảo: Các trang tài liệu MDN về Async JS và Promise. | Công ty Cổ phần Xincere (株式会社シンシア): Tại Xincere, công ty tuyển dụng và làm việc cùng các kỹ sư chưa có kinh nghiệm thực tế hoặc thực tập sinh kỹ sư (hằng năm có khoảng 100 ứng viên chưa có kinh nghiệm thực tế ứng tuyển và tham gia phỏng vấn kỹ thuật).
(参考文献:https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Async_JS/Introducing / https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Async_JS/Promises / https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise / https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Async_JS/Promises#async_%E3%81%A8_await | 株式会社シンシア:株式会社xincereでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。(シンシアでは、年間100人程度の実務未経験の方が応募し技術面接を受けます。))
📚 Bảng thuật ngữ IT & Hán Việt trong bài (重要IT用語集)
| Thuật ngữ (Kanji) | Furigana / Romaji | Âm Hán Việt | Ý nghĩa tiếng Việt | Cấp độ |
|---|---|---|---|---|
| 非同期 | ひどうき |
PHI ĐỒNG KỲ | Bất đồng bộ (Asynchronous) | Technical |
| 処理 | しょり |
XỬ LÝ | Xử lý, tác vụ | N3 |
| ふわっと | ふわっと |
- | Mơ hồ, chung chung (nghĩa bóng); nhẹ nhàng/bồng bềnh (nghĩa đen) | N2 |
| 同期処理 | どうきしょり |
ĐỒNG KỲ XỬ LÝ | Xử lý đồng bộ (Synchronous processing) | Technical |
| 実行 | じっこう |
THỰC HÀNH | Thực thi, chạy (code/chương trình) | N3 |
| 呼び出す | よびだす |
HÔ XUẤT | Gọi (hàm / API / xử lý) | N3 |
| 固まる | かたまる |
CỐ | Bị đơ, bị treo cứng (màn hình/ứng dụng) | N3 |
| 復帰 | ふっき |
PHỤC QUY | Quay trở lại, trả quyền điều khiển về (Return) | N2 |
| 配列 | はいれつ |
BÀI LIỆT | Mảng (Array) | Technical |
| 文字列 | もじれつ |
VĂN TỰ LIỆT | Chuỗi ký tự (String) | Technical |
| 条件分岐 | じょうけんぶんき |
ĐIỀU KIỆN PHÂN KỲ | Rẽ nhánh có điều kiện (Conditional branching: if/else, switch) | Technical |
| むやみやたらに | むやみやたらに |
- | Một cách bừa bãi, vô tội vạ, thiếu suy nghĩ | N1 |
| 切り分け | きりわけ |
THIẾT PHÂN | Sự phân định, bóc tách, khoanh vùng (vấn đề/trách nhiệm) | Technical |
| 応答 | おうとう |
ỨNG ĐÁP | Phản hồi, đáp trả (Respond) | N2 |
| シングルスレッド | しんぐるすれっど |
- | Đơn luồng (Single thread) | Technical |
| 関数 | かんすう |
HÀM SỐ | Hàm (Function) | Technical |
| 方式 | ほうしき |
PHƯƠNG THỨC | Phương thức, cơ chế | N2 |
| 待機 | たいき |
ĐÃI CƠ | Đang chờ (Pending / Standby) | N2 |
| 履行 | りこう |
LÝ HÀNH | Thực hiện hoàn tất, hoàn thành cam kết (Fulfilled) | N1 |
| 拒否 | きょひ |
CỰ PHỦ | Từ chối, thất bại (Rejected) | N2 |
| 一見 | いっけん |
NHẤT KIẾN | Thoạt nhìn, nhìn qua | N2 |
| かかりきり | かかりきり |
- | Bận tối mắt vào một việc duy nhất, bị chiếm dụng toàn bộ | N1 |
| 仕組み | しくみ |
SĨ TỔ | Cơ chế, cấu trúc vận hành | N3 |
| 実務未経験 | じつむみけいけん |
THỰC VỤ VỊ KINH NGHIỆM | Chưa có kinh nghiệm làm việc thực tế | N2 |
📐 Ngữ pháp tiếng Nhật IT bổ trợ (文法解説)
- 〜てくれる (
N4): Làm gì đó (có ích cho mình)- Cấu trúc:
V-て + くれる - Sắc thái sử dụng: いい感じにしてくれてる = Xử lý êm xuôi giúp cho lập trình viên.
- Cấu trúc:
- 〜くらいしか〜ない (
N3): Chỉ có thể… đến mức đó là cùng- Cấu trúc:
Mệnh đề / Danh từ + くらいしか + V-ない - Sắc thái sử dụng: Nhấn mạnh sự hạn chế trong câu trả lời khi chưa hiểu sâu.
- Cấu trúc:
- 〜たまま (
N3): Cứ để nguyên trạng thái…- Cấu trúc:
V-た + まま - Sắc thái sử dụng: 理由がふわっとしたままなので = Vì lý do cứ để nguyên ở trạng thái mơ hồ.
- Cấu trúc:
- 〜によって (
N3): Tùy thuộc vào…- Cấu trúc:
Danh từ + によって - Sắc thái sử dụng: 相手の状況や回線の速さによって = Tùy thuộc vào tình trạng phía đối phương và tốc độ mạng.
- Cấu trúc:
- 〜といった (
N3): Những thứ như là A, B…- Cấu trúc:
A や B といった + Danh từ - Sắc thái sử dụng: Dùng để liệt kê các ví dụ đại diện.
- Cấu trúc:
- 〜わけではない (
N3): Không hẳn là…, không có nghĩa là…- Cấu trúc:
Mệnh đề thông thường + わけではない - Sắc thái sử dụng: 処理が早くなるわけではなく = Không có nghĩa là tốc độ xử lý sẽ nhanh hơn.
- Cấu trúc:
- 〜とおり (
N3): Đúng như…- Cấu trúc:
Danh từ の / V-た + とおり - Sắc thái sử dụng: 引用にもあったとおり = Đúng như đã nêu trong phần trích dẫn.
- Cấu trúc:
- 〜だとしたら (
N2): Giả sử nếu là…- Cấu trúc:
Thể thông thường + としたら - Sắc thái sử dụng: これがWebアプリケーションだとしたら = Giả sử nếu đây là một ứng dụng Web thực tế.
- Cấu trúc: