2018-11-15

Hiểu JavaScript không đồng bộ - Vòng lặp sự kiện

Hiểu JavaScript không đồng bộ - Vòng lặp sự kiện

JavaScript là một ngôn ngữ lập trình đơn luồng có nghĩa là chỉ có một điều có thể xảy ra tại một thời điểm. Tức là, công cụ JavaScript chỉ có thể xử lý một câu lệnh tại một thời điểm trong một chuỗi duy nhất.
Mặc dù ngôn ngữ đơn luồng đơn giản hóa mã viết vì bạn không phải lo lắng về các vấn đề đồng thời, điều này cũng có nghĩa là bạn không thể thực hiện các hoạt động dài như truy cập mạng mà không chặn luồng chính.
Hãy tưởng tượng yêu cầu một số dữ liệu từ một API. Tùy thuộc vào tình hình máy chủ có thể mất một thời gian để xử lý yêu cầu trong khi chặn chủ đề chính làm cho trang web không hồi đáp.
Đó là nơi JavaScript không đồng bộ được phát. Sử dụng JavaScript không đồng bộ (chẳng hạn như callbacks, promise và async / await), bạn có thể thực hiện các yêu cầu mạng dài mà không chặn luồng chính.
Bạn không cần biết JavaScript hoạt động như thế nào dưới mui xe, nhưng thật hữu ích khi biết cách hoạt động của nó :)
Vì vậy, không cần thêm quảng cáo, hãy bắt đầu :)
Mẹo : Sử dụng Bit, bạn có thể biến bất kỳ mã JS nào thành API mà bạn có thể chia sẻ, sử dụng và đồng bộ hóa giữa các dự án và ứng dụng để xây dựng nhanh hơn và sử dụng lại nhiều mã hơn. Hãy thử một lần.

JavaScript đồng bộ hoạt động như thế nào?

Trước khi chúng ta đi sâu vào JavaScript không đồng bộ, trước tiên hãy hiểu cách mã JavaScript đồng bộ thực thi bên trong công cụ JavaScript. Ví dụ:
const second = () => { 
  console.log ('Xin chào!'); 
}
const first = () => { 
  console.log ('Xin chào!'); 
  thứ hai(); 
  console.log ('Kết thúc'); 
}
Đầu tiên();
Để hiểu cách mã trên thực thi bên trong công cụ JavaScript, chúng ta phải hiểu khái niệm ngữ cảnh thực thi và ngăn xếp cuộc gọi (còn được gọi là ngăn xếp thực hiện).

Ngữ cảnh thực thi

Một bối cảnh thực thi là một khái niệm trừu tượng về môi trường nơi mã JavaScript được đánh giá và thực hiện. Bất cứ khi nào bất kỳ mã nào được chạy trong JavaScript, nó chạy bên trong một ngữ cảnh thực thi.
Mã hàm thực thi bên trong ngữ cảnh thực thi hàm và mã toàn cục thực hiện bên trong ngữ cảnh thực thi chung. Mỗi hàm có ngữ cảnh thực thi riêng của nó.

Ngăn xếp cuộc gọi

Ngăn xếp cuộc gọi như tên gọi của nó ngụ ý là một ngăn xếp với cấu trúc LIFO (Last in, First out), được sử dụng để lưu trữ tất cả ngữ cảnh thực thi được tạo ra trong quá trình thực thi mã.
JavaScript có một ngăn xếp cuộc gọi duy nhất vì nó là một ngôn ngữ lập trình đơn luồng. Ngăn xếp cuộc gọi có cấu trúc LIFO có nghĩa là các mục có thể được thêm hoặc xóa khỏi đầu ngăn xếp chỉ.
Hãy quay lại đoạn mã trên và cố gắng hiểu cách mã thực thi bên trong công cụ JavaScript.
const second = () => { 
  console.log ('Xin chào!'); 
}
const first = () => { 
  console.log ('Xin chào!'); 
  thứ hai(); 
  console.log ('Kết thúc'); 
}
Đầu tiên();
Ngăn xếp cuộc gọi cho mã ở trên

Vậy điều gì đang xảy ra ở đây?

Khi mã này được thực hiện, một ngữ cảnh thực thi toàn cục được tạo ra (được biểu thị bằng main()) và được đẩy lên trên cùng của ngăn xếp cuộc gọi. Khi first()gặp phải cuộc gọi , nó được đẩy lên đầu ngăn xếp.
Tiếp theo, console.log('Hi there!')được đẩy lên trên cùng của ngăn xếp, khi nó kết thúc, nó xuất hiện từ ngăn xếp. Sau đó, chúng tôi gọi second(), vì vậy second()chức năng được đẩy lên trên cùng của ngăn xếp.
console.log('Hello there!')được đẩy lên trên cùng của ngăn xếp và bật ra khỏi ngăn xếp khi nó kết thúc. Các second()chức năng kết thúc, vì vậy nó popped ra khỏi stack.
console.log(‘The End’)được đẩy lên đầu ngăn xếp và bị xóa khi nó kết thúc. Sau đó, first()hàm hoàn thành, do đó nó bị loại bỏ khỏi ngăn xếp.
Chương trình hoàn thành thực hiện tại thời điểm này, vì vậy ngữ cảnh thực thi toàn cục ( main()) được bật ra khỏi ngăn xếp.

JavaScript không đồng bộ hoạt động như thế nào?

Bây giờ chúng ta có một ý tưởng cơ bản về ngăn xếp cuộc gọi và cách JavaScript hoạt động đồng bộ, hãy quay lại JavaScript không đồng bộ.

Chặn là gì?

Giả sử chúng ta đang xử lý hình ảnh hoặc yêu cầu mạng theo cách đồng bộ. Ví dụ:
const processImage = (image) => { 
  / ** 
  * thực hiện một số thao tác trên ảnh 
  ** / 
  console.log ('Xử lý ảnh'); 
}
const networkRequest = (url) => { 
  / ** 
  * yêu cầu tài nguyên mạng 
  ** / 
  trả về someData; 
}
const greeting = () => { 
  console.log ('Hello World'); 
}
processImage (logo.jpg); 
networkRequest ('www.somerandomurl.com'); 
Lời chào();
Việc xử lý hình ảnh và yêu cầu mạng cần có thời gian. Vì vậy, khi processImage()chức năng được gọi, nó sẽ mất một thời gian tùy thuộc vào kích thước của hình ảnh.
Khi processImage()hàm hoàn thành, nó sẽ bị xóa khỏi ngăn xếp. Sau đó networkRequest()hàm được gọi và được đẩy vào ngăn xếp. Một lần nữa nó cũng sẽ mất một thời gian để hoàn thành thực hiện.
Cuối cùng khi networkRequest()hàm hoàn thành, greeting()hàm được gọi và vì nó chỉ chứa một console.logcâu lệnh và các console.logcâu lệnh nói chung là nhanh, do đó greeting()hàm được thực hiện ngay lập tức và trả về.
Vì vậy, bạn thấy, chúng ta phải đợi cho đến khi hàm (như processImage()hoặc networkRequest()) đã kết thúc. Điều này có nghĩa là các chức năng này đang chặn ngăn xếp cuộc gọi hoặc chuỗi chính. Vì vậy, chúng tôi không thể thực hiện bất kỳ thao tác nào khác trong khi mã trên thực thi không phải là lý tưởng.

Vậy giải pháp là gì?

Giải pháp đơn giản nhất là gọi lại không đồng bộ. Chúng tôi sử dụng gọi lại không đồng bộ để làm cho mã của chúng tôi không bị chặn. Ví dụ:
const networkRequest = () => { 
  setTimeout (() => { 
    console.log ('Mã không đồng bộ'); 
  }, 2000); 
};
console.log ('Hello World');
networkRequest ();
Ở đây tôi đã sử dụng setTimeoutphương pháp để mô phỏng yêu cầu mạng. Xin lưu ý rằng setTimeoutđây không phải là một phần của công cụ JavaScript, nó là một phần của một cái gì đó được gọi là API web (trong trình duyệt) và API C / C ++ (trong node.js).
Để hiểu cách mã này được thực thi, chúng ta phải hiểu thêm một vài khái niệm vòng lặp sự kiện như vậy và hàng đợi gọi lại (hoặc hàng đợi thông báo).
Tổng quan về môi trường chạy JavaScript
Vòng lặp sự kiện, API web và hàng đợi thông báo không phải là một phần của công cụ JavaScript, nó là một phần của môi trường chạy JavaScript của trình duyệt hoặc môi trường chạy JavaScript Nodejs (trong trường hợp Nodejs). Trong Nodejs, các API web được thay thế bằng các API C / C ++.
Bây giờ chúng ta hãy quay trở lại đoạn code trên và xem nó được thực hiện như thế nào theo một cách không đồng bộ.
const networkRequest = () => { 
  setTimeout (() => { 
    console.log ('Mã không đồng bộ'); 
  }, 2000); 
};
console.log ('Hello World');
networkRequest ();
console.log ('Kết thúc');
Vòng lặp sự kiện
Khi mã trên tải trong trình duyệt, mã console.log(‘Hello World’)được đẩy vào ngăn xếp và bật ra khỏi ngăn xếp sau khi hoàn tất. Tiếp theo, một cuộc gọi networkRequest()được gặp phải, vì vậy nó được đẩy lên trên cùng của ngăn xếp.
setTimeout()Hàm tiếp theo được gọi, vì vậy nó được đẩy lên trên cùng của ngăn xếp. Có setTimeout()hai đối số: 1) gọi lại và 2) thời gian tính bằng mili giây (mili giây).
Các setTimeout()phương pháp khởi động một bộ đếm thời gian của 2strong môi trường API web. Tại thời điểm này, setTimeout()đã hoàn thành và nó bật ra khỏi ngăn xếp. Sau đó, console.log('The End')được đẩy vào ngăn xếp, thực hiện và gỡ bỏ khỏi ngăn xếp sau khi hoàn thành nó.
Trong khi đó, bộ hẹn giờ đã hết hạn, bây giờ gọi lại được đẩy đến hàng đợi tin nhắn . Nhưng cuộc gọi lại không được thực hiện ngay lập tức, và đó là nơi mà vòng lặp sự kiện khởi động.

Vòng lặp sự kiện

Công việc của vòng lặp Sự kiện là nhìn vào ngăn xếp cuộc gọi và xác định xem ngăn xếp cuộc gọi có trống hay không. Nếu ngăn xếp cuộc gọi trống, nó sẽ nhìn vào hàng đợi tin nhắn để xem liệu có bất kỳ cuộc gọi đang chờ xử lý nào đang chờ được thực hiện hay không.
Trong trường hợp này, hàng đợi tin nhắn chứa một cuộc gọi lại và ngăn xếp cuộc gọi trống tại thời điểm này. Vì vậy, vòng lặp Sự kiện đẩy cuộc gọi lại đến đầu ngăn xếp.
Sau đó, nó console.log(‘Async Code’)được đẩy lên trên cùng của ngăn xếp, được thực thi và bật ra khỏi ngăn xếp. Tại thời điểm này, cuộc gọi lại đã kết thúc để nó bị xóa khỏi ngăn xếp và chương trình cuối cùng cũng kết thúc.
Các tin nhắn hàng đợi cũng chứa các callbacks từ các sự kiện DOM như các sự kiện nhấp chuột và các sự kiện bàn phím. Ví dụ:
document.querySelector ('. btn'). addEventListener ('click', (sự kiện) => { 
  console.log ('Button Clicked'); 
});
Trong trường hợp các sự kiện DOM, trình lắng nghe sự kiện nằm trong môi trường API web đang chờ một sự kiện nào đó (sự kiện nhấp chuột trong trường hợp này) xảy ra, và khi sự kiện đó xảy ra, thì hàm gọi lại được đặt trong hàng đợi tin nhắn đang đợi để được thực thi .
Một lần nữa vòng lặp sự kiện sẽ kiểm tra xem ngăn xếp cuộc gọi có trống không và đẩy sự kiện gọi lại đến ngăn xếp nếu nó trống và gọi lại được thực thi.

Trì hoãn thực hiện hàm

Chúng ta cũng có thể sử dụng setTimeoutđể trì hoãn việc thực thi hàm cho đến khi ngăn xếp rõ ràng. Ví dụ:
const bar = () => { 
  console.log ('bar'); 
}
const baz = () => { 
  console.log ('baz'); 
}
const foo = () => { 
  console.log ('foo'); 
  setTimeout (thanh, 0); 
  baz (); 
}
foo ();
Các bản in mã:
foo 
baz 
bar
Khi mã này chạy, đầu tiên foo()được gọi, bên trong foochúng ta gọi console.log('foo'), sau đó setTimeout()được gọi với bar()như là một cuộc gọi lại và 0 secondshẹn giờ.
Bây giờ nếu chúng ta chưa sử dụng setTimeoutbar()hàm sẽ được thực thi ngay lập tức, nhưng việc sử dụng setTimeoutvới bộ đếm thời gian 0 giây sẽ giúp trì hoãn việc thực hiện barcho đến khi ngăn xếp trống.
Sau 0 giây, bar() cuộc gọi lại được đưa vào hàng đợi tin nhắn đang đợi để được thực hiện. Nhưng nó sẽ chỉ được thực hiện khi ngăn xếp hoàn toàn trống rỗng sau khi chức năng bazvà foochức năng kết thúc.

Hàng đợi công việc ES6

Chúng ta đã học được cách các cuộc gọi lại không đồng bộ và các sự kiện DOM được thực thi, sử dụng hàng đợi thông điệp để lưu trữ tất cả các cuộc gọi lại chờ đợi để được thực thi.
ES6 giới thiệu khái niệm về hàng đợi công việc được sử dụng bởi Promises trong JavaScript. Sự khác biệt giữa hàng đợi tin nhắn và hàng đợi công việc là hàng đợi công việc có mức độ ưu tiên cao hơn hàng đợi tin nhắn, có nghĩa là các công việc hứa hẹn bên trong hàng đợi công việc sẽ được thực hiện trước cuộc gọi lại bên trong hàng đợi tin nhắn.
Ví dụ:
const bar = () => { 
  console.log ('bar'); 
};
const baz = () => { 
  console.log ('baz'); 
};
const foo = () => { 
  console.log ('foo'); 
  setTimeout (thanh, 0); 
  new Promise ((giải quyết, từ chối) => { 
    resolve ('Promise resolve'); 
  }). sau đó (res => console.log (res)) 
    .catch (err => console.log (err)); 
  baz (); 
};
foo ();
Các bản in mã:
foo 
baz Đã 
giải quyết 
thanh được giải quyết
Chúng ta có thể thấy rằng lời hứa được thực hiện trước đó setTimeout, bởi vì đáp ứng lời hứa được lưu trữ bên trong hàng đợi công việc có mức độ ưu tiên cao hơn hàng đợi thông báo.

Phần kết luận

Vì vậy, chúng tôi đã học được cách JavaScript không đồng bộ hoạt động và các khái niệm khác như ngăn xếp cuộc gọi, vòng lặp sự kiện, hàng đợi tin nhắn và hàng đợi công việc cùng nhau tạo môi trường chạy JavaScript. Mặc dù bạn không cần phải học tất cả các khái niệm này để trở thành một nhà phát triển JavaScript tuyệt vời nhưng sẽ rất hữu ích khi biết các khái niệm này :)
Đó là nó và nếu bạn thấy bài viết này hữu ích, hãy nhấp vào nút vỗ tay, bạn cũng có thể theo tôi trên Trung bình và Twitter , và nếu bạn có bất kỳ nghi ngờ nào, hãy bình luận! Tôi rất sẵn lòng trợ giúp :)