Async JS toàn diện

Jan 27 2023
MDN định nghĩa quy trình làm việc không đồng bộ là ..

MDN định nghĩa quy trình làm việc không đồng bộ là

..bắt đầu một tác vụ có khả năng chạy lâu dài và vẫn có thể phản hồi các sự kiện khác trong khi tác vụ đó chạy… Khi tác vụ đó hoàn thành, chương trình của bạn sẽ hiển thị kết quả.

Nhưng chính xác làm thế nào điều này có thể đạt được trên web với JavaScript?

Trước khi đi sâu vào triển khai async, bạn nên hiểu cơ bản về JS ở cấp độ thấp hơn.

JS hoạt động như thế nào?

JavaScript là một luồng đơn và được diễn giải, nghĩa là các hướng dẫn được thực thi trực tiếp và không có bước biên dịch trước. Hãy lấy V8, động cơ phổ biến nhất làm ví dụ.

Ở cấp độ cao, V8 phân tích cú pháp mã, tạo cây cú pháp trừu tượng (AST) và biên dịch nó thành mã byte được tối ưu hóa bằng trình thông dịch. Ngoài ra còn có một trình chuẩn bị, hai trình biên dịch và bộ nhớ đệm giúp tốc độ nhanh (V8 có các blog tuyệt vời về điều này). Nó chỉ cung cấp cho chúng ta một ngăn xếp cuộc gọi, đống và vòng lặp sự kiện ( đại loại là không hẳn vậy) . Chúng ta có thể bỏ qua heap vì mục đích của lỗ thỏ này: đó là một đoạn bộ nhớ phi cấu trúc với các tham chiếu đối tượng và biến.

Vòng lặp sự kiện thời gian chạy

Không giống như Java hay C++, JS cho phép thực thi một sự kiện tại một thời điểm. Chúng tôi có thể thấy điều này trong trình duyệt của mình bằng cách theo dõi đường dẫn cuộc gọi với console.trace():

// shoutout to ES6 arrow functions

foo = () => {
    console.log("Hello, World!");
    bar();
}
bar = () => {
    console.log("Goodbye, World!");
    trace();
}

trace = () => console.trace();

foo();

Hello, World!   VM1049:6
Goodbye, World! VM1049:11 

// console.trace
trace @ VM1049:11
bar @ VM1049:7
foo @ VM1049:3

      
                
call stack visualization

Đặt thời gian chờ

Hãy chạy các phương thức tương tự với độ trễ không đồng bộ là 5000 ms với setTimeout().

foo = () => {
    console.log("Hello, World!");
     setTimeout(bar, 5000);
}

bar = () => {
    console.log("Goodbye, World!");
    trace();
}

trace = () => console.trace();

foo();

Hello, World!   VM1526:2

// 3 days later... 
Goodbye, World!  VM1526:6

// console.trace 
trace @ VM1382:10
bar @ VM1382:6

// setTimeout is invoked
b @ main-base.bundle.UM3…oFn_e5gQ.12.js:1723
setTimeout (async)  
Cga.window.setTimeout @ main-base.bundle.UM3…toFn_e5gQ.12.js:516

// foo is pushed to the stack first
foo @ VM1382:3

setTimeoutkhông phải là một phần của công cụ JS, điều này hợp lý vì công cụ có thể xử lý một sự kiện tại một thời điểm. Tuy nhiên, các trình duyệt là đa luồng và cung cấp cho chúng tôi các API web để truy cập các triển khai setTimeout. Ví dụ: Chrome sử dụng công cụ V8 đơn luồng trong kiến ​​trúc đa quy trình, trong đó mọi quy trình đều đa luồng. Trong trường hợp này, điều này được gọi rõ ràng với Cga.setTimeout @ main-base.bundle.blah-blah-blah...Virtually, điều này cho phép chúng tôi sử dụng nhiều luồng.

Vòng lặp sự kiện

Hãy sắp xếp lại khối mã của chúng tôi:

foo = () => {
    console.log("Hello, World!");
     setTimeout(bar, 5000);
}

bar = () => {
    setTimeout(() => console.log("Goodbye, World!"), 5000);
    trace();
}

trace = () => console.trace();

foo();

// output of foo 
Hello, World! VM1649:11 

// console.trace 
trace @ VM1694:11
bar @ VM1694:8
b @ main-base.bundle.UM3…oFn_e5gQ.12.js:1723
setTimeout (async)  
Cga.window.setTimeout @ main-base.bundle.UM3…toFn_e5gQ.12.js:516
foo @ VM1694:3
// end trace 

// 3 days later...
VM1649:6 Goodbye, World!

JRE bao gồm:

  • Công cụ JS (đơn luồng)
  • Máy ảo Java (JVM) — Máy ảo thực thi mã trong thời gian chạy. Nhân tiện, đây là lý do tại sao “VM” thêm vào trước nhật ký theo dõi.
  • API web
  • hàng đợi nhiệm vụ
  • vòng lặp sự kiện

Ngăn xếp cuộc gọi của công cụ của chúng tôi đang bật từng sự kiện một. Sau khi thấy setTimeout(), phương thức này cùng với lệnh gọi lại (tham chiếu hàm) sẽ được ủy quyền cho API web.

Lưu ý rằng công cụ không còn quan tâm đến cách triển khai setTimeout của API xử lý sự kiện và gọi lại. Công cụ chuyển sang bật sự kiện tiếp theo và cung cấp sự kiện đó cho Pacman (JVM thực thi mã).

Trong thời gian chờ đợi, API sử dụng các luồng của nó để đợi 5000 ms và sau đó chuyển nó vào hàng đợi tác vụ. Vòng lặp sự kiện thấy rằng sự kiện được sắp xếp và đẩy nó trở lại ngăn xếp cuộc gọi khi và chỉ khi ngăn xếp cuộc gọi trống.

Bạn có thể sử dụng trình hiển thị thú vị của Philip Roberts để xem tất cả hoạt động. Ngoài ra, hãy xem bài nói chuyện tuyệt vời của anh ấy tại JSConf vào năm 2014.