Asynchronous trong JavaScript hoạt động thế nào?

chào mấy anh em mới học js đọc xong khái niệm về asyn em khá mơ hồ về cách nó thực thi code ạ , theo như tìm hiểu thì js chỉ hổ trợ single-thread nên asyn được tạo ra nhằm mục đích giúp giảm tổng thời gian thực thi các tác vụ mà ko cần tạo ra luồng mới ,
https://viblo.asia/p/hieu-ve-asynchronous-javascript-RQqKLARmZ7z

http://mahpahh.com/vong-lap-su-kien-trong-javascript

em đọc qua 2 blog này thì thấy người ta hay sử dụng hàm settimeout để làm ví dụ minh họa , nhưng trong thực tế thay 1 hàm khác không phải là settimeout thì nó lại chạy không đúng với mong đợi của em các anh có thể giải thích hộ em
vd:

function doHomework(subject, callback) {
console.log(`Starting my ${subject} homework.`);
callback();
}

console.log('Hello World');
doHomework('math', function() {
while (true){}
})
console.log('End Code');

khi chạy đoạn code trên điều em mong đợi là : sẽ xuất ra hello world , end code và callback kia sẽ chạy vòng lặp vô hạn mà ko ảnh hưởng đến các hàm phía dưới
nhưng thực tế thì nó chỉ xuất ra hello word , Starting my math homework. mà ko xuất ra end code , vậy ko đúng với mong đợi là bất đồng bộ giúp thực thi nhiều tác vụ đồng thời

xong em cho settimeout vào
vd:

function doHomework(subject, callback) {
console.log(`Starting my ${subject} homework.`);
setTimeout(function () {
	callback();
}, 0);
}

console.log('Hello World');
doHomework('math', function() {
while (true){}
})

setTimeout(function () {
console.log('run after 3s');
}, 3000);

console.log('End Code');

lần này thì code lại chạy ra Hello World,End Code rồi chạy trong vô tận khác với mong đợi của em là Hello World,End Code , run after 3s , chạy vô tận

ở ví dụ trên thì em dùng vòng lặp vô tận để đại diện cho 1 tác vụ trong thực tế có thời gian thực thi là rất lâu thì các callback hoặc câu lệnh khác cũng phải chờ tác vụ đó chứ có được thực thi bất đồng bộ đâu nhỉ ?

  1. Không phải :slight_smile: Nếu bạn muốn async bạn phải sử dụng đối tượng Promise và các API đặc biệt (bao gồm setTimeout/setInterval) :slight_smile:
  2. JS vẫn là single-threaded nên nó sẽ chạy hàm lặp vô tận trước (setTimeout 0). Còn để chạy song song thì phải spawn Web Worker.

Bất đồng bộ không có nghĩa là song song.

5 Likes

vậy bất đồng bộ lúc thực thi code có giống đa luồng không anh ? nếu đa luồng nó sẽ chạy theo kiểu chạy luồng này 1 tí xong nhảy sang luồng kia chạy 1 tí cứ vậy cho đến khi chạy xong thì bất đồng bộ làm sao để thực thi nhiều tác vụ được ?

Cũng không giống luôn.

5 Likes

Lập trình bất đồng bộ là là kiểu lập trình theo hướng bất đồng bộ. Quy trình bất đồng bộ là quy trình thực hiện nhiều công việc cùng một lúc. Quy trình bất đồng bộ có thể chia làm 2 loại: Đơn luồng và đa luồng.

Quy trình bất đồng bộ đa luồng là xử lí tác vụ trong nhiều luồng, theo kiểu nhiều tác vụ xử lí cùng lúc, song song.

Quy trình bất đồng bộ đơn luồng thì phức tạp hơn nhiều. Anh không chắc là mình giải thích đúng về lập trình bất đồng bộ trong Javascript (đơn luồng) không, nhưng thôi kệ vậy :sweat_smile:.

Quá trình xử lí bất đồng bộ trong Javascript gồm các thành phần sau (anh lười quá nên copy trên mạng :sweat_smile:) :

  • CALL STACK - là một dạng cấu trúc dữ liệu ghi lại vị trí các lệnh đang được thực hiện trong chương trình. Khi lệnh bắt đầu được thực hiện sẽ được đưa vào đỉnh của stack và sau khi thực hiện xong sẽ được lấy ra khỏi ngăn xếp.

  • WEB APIs - vể bản chất đây chính là các thread mà ta không thể truy cập trực tiếp mà chỉ có thể gọi được đến nó. Các thread này do trình duyệt cung cấp.

  • CALLBACK QUEUE - là một dạng cấu trúc dữ liệu với nguyên tắc First-In-First-Out (vào trước ra trước).

  • EVENT LOOP - có nhiệm vụ giám sát tình trạng của CALL STACK và CALLBACK QUEUE.

Theo cách anh hiểu thì khi chạy thì task sẽ được lưu tuần tự vào trong Call Stack. Đối với những task không dùng API mà Web APIs cung cấp thì máy cho ra kết quả luôn. Còn đối với những task dùng API mà Web APIs cung cấp thì:

  1. Task đó được đưa vào Web APIs.

  2. Sau đó, task đó được xử lí trong Web APIs, xử lí xong rồi thì được chuyển xuống Callback Queue.

  3. Event Loop liên tục giám sát xem Callback Queue có gì không và Call Stack đã trống chưa. Sau khi phát hiện ra task đó ở trong Callback Queue và Call Stack đã trống thì Event Loop đẩy Callback Queue vào Call Stack và thực hiện task. Lưu ý rằng khi Call Stack còn task thì Event Loop sẽ chờ cho đến khi Call Stack trống thì mới đẩy task đó vào Call Stack.

  4. Lưu ý: Thứ tự các kết quả được trả về từ Web APIs sang Callback Queue sẽ không theo thứ tự đưa vào mà sẽ tùy thuộc task nào chạy xong trước sẽ được đẩy vào Callback Queue trước đồng nghĩa với việc sẽ được chuyển qua Call Stack trước.

5 Likes

Dù không cùng một ngôn ngữ, nhưng topic này cũng có thể cho bạn một cái nhìn rõ ràng hơn về “bất đồng bộ”, khái niệm được cụ thể hoá lần đầu tiên trong C# rồi mới đến các ngôn ngữ khác.

4 Likes

Cái đầu tiên không phải async nên sẽ thực thi từ trên xuống dưới.
Cái tiếp theo là async nên tất cả câu lệnh thực thi “cùng lúc”, nghĩa là nó không chạy từ trên xuống dưới nữa. Không tin bạn cho 2 lệnh timeout 3s chạy xem nó hoàn thành trong 3s như 1 vị thần :relieved::relieved:. Sau timeout 0ms thì chương trình bị block trong vòng lặp vô tận, tiếp theo thì không có tiếp theo nữa :slightly_smiling_face:.
1 thứ không bao giờ được quên: đừng động đến synchronous trong asynchronous trừ khi bạn hiểu rõ mình đang làm cái gì :smirk::smirk:
Dùng vòng lặp vô tận không chứa gì để đại diện 1 tác vụ tốn thời gian. Bạn nghĩ nó đúng nhưng thực tế lại là sai vì vòng lặp như vậy là sync, nó chiếm 1 thread duy nhất để chạy chương trình rồi. Timeout nó bảo chương trình là “chờ tao một lúc, lúc chờ thì mày làm việc tiếp theo đi, xong thì gọi tao lại”. Thử cho timeout vào trong vòng lặp xem điều kỳ diệu gì xảy ra (đoạn này mình đoán thôi, vì js nó dị lắm, chạy thử mới biết được)

4 Likes

Một video về lập trình bất đồng bộ trong Javascript, nếu cảm thấy trình độ tiếng Anh của mình đủ thì hãy thử xem video bên dưới :sunglasses: :

2 Likes

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

em đã thử thay vì dùng vòng lặp vô tận thì em viết 1 hàm fibonaci rồi cho nó 1 số thật to kết quả hàm đó vẫn thực thi tuần tự (tức là khi mà hàm fibonaci chạy mất khoảng 2-3s thì dù có chạy các hàm settimeout 1s, 0.5s thì nó vẫn đợi hàm fibonaci chạy xong thì nó mới chạy các hàm settimeout kia) , có nghĩa là đúng như lý thuyết anh đưa ra chỉ những api mà Web APIs cung cấp thì mới bất đồng bộ được , còn lại thì nó làm việc tuần tự hết cho dù có tạo promise rồi truyền callback vào

Đính chính chút, NodeJS vẫn là multithread bình thường, 1 thread luôn luôn có cho JavaScript, các thread khác (nếu có) là của C++.

Lý do họ quảng cáo NodeJS là single thread vì họ giới hạn phạm vi NodeJS chỉ có phần JavaScript, mà nếu chỉ nói bên JavaScript không thì đương nhiên chỉ có 1 thread.

Bạn cần nắm thêm phần này để đọc Event Loop cho dễ.

9 Likes

Fibonacci vẫn block thread, muốn làm những việc nặng như vậy thì bạn có khái niệm Web Workers. Mình không chắc có thể kết hợp Web Worker và Promise với nhau không nên mình không đụng đến.

Còn lại, bạn cần sử dụng những api trả về promise và kết hợp chúng lại. Thực tế, bạn cũng chỉ cần những api đó cho ứng dụng web thôi!
VD: fetch 2 github user cùng lúc.

async function getUserAsync(name) 
{
  let response = await fetch(`https://api.github.com/users/${name}`);
  let data = await response.json()
  return data;
}

getUserAsync('dungph')
  .then(data => console.log(data)); 

getUserAsync('withoutboats')
  .then(data => console.log(data)); 

Async trong lập trình là một vấn đề khó, nên bạn phải tìm hiểu từ từ thôi :slightly_smiling_face:

4 Likes

Promise, async/await, setTimeout, setInterval,…đều dùng Web APIs nha em.

2 Likes

bạn có ví dụ nào mà việc thực thi hàm callback diễn ra ngay trên máy hiện tại ko , vì mình thấy mấy ví dụ trên gg toàn là các hàm callback hoặc là settimeout thì trong thời gian timeout đó ko thực thi câu lệnh nào , hoặc là gọi đến 1 api kết nối database (hoặc đợi response của request) thì thời gian đó cũng ko thực thi câu lệnh nào hoàn toàn là chờ kết quả trả về

1 Like

Chẳng ai dùng Javascript kết nối tới db đâu, họ liên kết cái db đó với một cái server, và user sẽ kết nối tới cái server đó. Lí do vì sao, mời em search: Why we don't use frontend to connect db?

Vừa chạy setTimeout, vừa chạy câu lệnh khác? Ez :sunglasses:

(async () => {
    setTimeout(_ => {
        console.log(`Print after 1s`)
    }, 1000)
    console.log(`Print now`)
    fetch(url1)
    fetch(url2)
})()

Vừa chờ kết quả trả về từ request lên server, vừa thực hiện câu lệnh khác? Cũng ez nốt :sunglasses:

(async _ => {
    let csrf_token = $("#csrf_token").val()
    $.post(url1, {
        csrf_token: csrf_token,
        data: data_for_url1
    }
    $.post(url2, {
        csrf_token: csrf_token,
        data: data_for_url2
    }
    console.log(`Start request`)
})()

Rồi, anh còn có 1 ví dụ nữa :rofl:

(async () => {
    while(true){
        $.get(HTTP_Flood_website)
    }
    //Sau khi chạy đoạn code này, hãy thử mở DevsTool lên, vào tab Network và thấy điều kì diệu xảy ra :))
})()

Người ta thường làm ví dụ kiểu đó vì những cái đó gần với thực tế. Ví dụ, ở thực tế thì chúng ta sẽ muốn sau khi có response của request thì mới chạy câu lệnh khác, vì nếu không có cái response thì thiếu dữ liệu để chạy tiếp.

5 Likes

Mấy cái sử dụng cpu-bound như tính toán fibonaci thì ko dùng được bất đồng bộ đâu.
Chỉ I/O-bound mới xài đc async thôi như chờ request phản hồi hay đọc file gì đấy…
Trường hợp phải tính toán , resize ảnh … nặng về cpu thì e có thể dùng web workers hoặc WebAssembly.

10 Likes

Bạn có misconception về Async.

Bản chất của async không phải tính toán nhiều thứ cùng lúc, đó là việc của parallel computing.
Asynchronous không làm cho việc tính toán nhanh hơn hay làm nhiều việc hơn mà nó giúp giảm bớt thời gian chờ đợi vô ích. Ví dụ như khi chờ timeout, trong lúc đó, thay vì không làm gì thì chương trình bỏ qua đoạn đó để chạy sau và thực hiện câu lệnh khác.

1 Promise có thể hiểu nôm na là 1 hàm trả về 1 trong 2 loại giá trị, 1 loại là Ready(<giá trị trả về>) và 1 loại là Pending chẳng hạn. Nếu hàm trả về Ready(…) - (khi có response hay khi hết timeout, …) thì callback sẽ được gọi (hoặc trong async/await, nó sẽ trả về giá trị), nếu hàm trả về Pending thì chương trình bỏ qua đoạn đó để thực hiện sau.

Sau một hồi lần mò để có thể implement 1 promise như vậy bằng tay thì câu trả lời là: Không thể. Chỉ có thể sử dụng các api có sẵn.

8 Likes

@phamvandung Bình luận của bạn khá đúng rồi, mình chỉ bổ sung thêm 2 ý sau:

  1. Khi chạy xong, Promise sẽ có trạng thái là Fulfilled khi thành công và Rejected khi thất bại. Khi ở trạng thái Rejected thì Promise sẽ trả lỗi về. Nếu không dùng try…catch hoặc catch() thì sẽ có một lỗi hiển thị ra.

  2. Thật ra, vẫn có cách tạo Promise không dùng Promise API có sẵn, thử search: Promise polyfill xem, mà thường thì người ta sẽ dùng Babel nhưng đối với các trình duyệt hiện tại thì Babel khá là vô dụng, người ta chỉ xài Babel khi họ muốn code của mình tương thích với các trình duyệt cũ (trước năm 2015)

6 Likes

Sync hay Async nó liên quan mật thiết đến blocking I/O và non-blocking I/O. Vì thế phải đặt nó vào ngữ cảnh của I/O thì mới rõ ràng.

6 Likes

Hãy nói về một vấn đề có liên quan: Javascript là cái gì? Nói một cách đơn giản, Javascript là một ngôn ngữ lập trình:
“high-level, single-threaded, garbage-collected, interpreted (or just-in-time compiled), prototype-based, multi-paradigm, dynamic language with a non-blocking event loop" (trích từ Fireship) :sunglasses:. Tạm dịch là:
“cấp cao, đơn luồng, garbage-collected (quản lý bộ nhớ tự động dạng thu gom rác), thông dịch (hoặc là biên dịch tại chỗ), dựa trên khuôn mẫu (prototype), đa mô hình, ngôn ngữ lập trình động với một non-blocking event loop” :sunglasses:

5 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?