Gỡ lỗi JavaScript không đồng bộ bằng Công cụ của Chrome cho nhà phát triển

Giới thiệu

Một tính năng mạnh mẽ khiến JavaScript trở nên độc đáo là khả năng hoạt động không đồng bộ thông qua các hàm gọi lại. Việc chỉ định lệnh gọi lại không đồng bộ cho phép bạn viết mã do sự kiện điều khiển, nhưng cũng khiến việc theo dõi lỗi trở nên khó khăn vì JavaScript không thực thi theo cách tuyến tính.

May mắn thay, giờ đây, trong Công cụ dành cho nhà phát triển của Chrome, bạn có thể xem ngăn xếp lệnh gọi đầy đủ của các lệnh gọi lại JavaScript không đồng bộ!

Thông tin tổng quan nhanh về ngăn xếp lệnh gọi không đồng bộ.
Thông tin tổng quan nhanh về ngăn xếp lệnh gọi không đồng bộ. (Chúng tôi sẽ sớm phân tích quy trình của bản minh hoạ này.)

Sau khi bật tính năng ngăn xếp lệnh gọi không đồng bộ trong Công cụ cho nhà phát triển, bạn sẽ có thể đi sâu vào trạng thái của ứng dụng web tại nhiều thời điểm. Đi theo dấu vết ngăn xếp đầy đủ cho một số trình nghe sự kiện, setInterval,setTimeout, XMLHttpRequest, lời hứa, requestAnimationFrame, MutationObservers, v.v.

Khi đi theo dấu vết ngăn xếp, bạn cũng có thể phân tích giá trị của bất kỳ biến nào tại thời điểm cụ thể đó của quá trình thực thi thời gian chạy. Đây giống như một cỗ máy thời gian cho các biểu thức đồng hồ!

Hãy bật tính năng này và xem một số trường hợp sau.

Bật tính năng gỡ lỗi không đồng bộ trong Chrome

Hãy dùng thử tính năng mới này bằng cách bật tính năng này trong Chrome. Chuyển đến bảng điều khiển Sources (Nguồn) của Chrome Canary DevTools.

Bên cạnh bảng điều khiển Call Stack (Ngăn xếp lệnh gọi) ở bên phải, có một hộp đánh dấu mới cho "Async" (Không đồng bộ). Bật hoặc tắt tính năng gỡ lỗi không đồng bộ bằng cách bật/tắt hộp đánh dấu. (Mặc dù sau khi bật, bạn có thể không bao giờ muốn tắt tính năng này.)

Bật hoặc tắt tính năng không đồng bộ.

Ghi lại các sự kiện bộ hẹn giờ bị trễ và phản hồi XHR

Có thể bạn đã thấy thông báo này trong Gmail:

Gmail đang thử gửi lại email.

Nếu xảy ra sự cố khi gửi yêu cầu (máy chủ gặp sự cố hoặc có vấn đề về kết nối mạng ở phía máy khách), Gmail sẽ tự động thử gửi lại thư sau một khoảng thời gian chờ ngắn.

Để xem cách ngăn xếp lệnh gọi không đồng bộ có thể giúp chúng ta phân tích các sự kiện hẹn giờ bị trì hoãn và phản hồi XHR, tôi đã tạo lại luồng đó bằng một ví dụ mô phỏng Gmail. Bạn có thể tìm thấy mã JavaScript đầy đủ trong đường liên kết ở trên, nhưng quy trình sẽ như sau:

Biểu đồ quy trình của ví dụ về Gmail mô phỏng.
Trong sơ đồ ở trên, các phương thức được làm nổi bật bằng màu xanh dương là những vị trí chính để tính năng DevTool mới này mang lại nhiều lợi ích nhất vì các phương thức này hoạt động không đồng bộ.

Khi chỉ xem bảng điều khiển ngăn xếp lệnh gọi trong các phiên bản DevTools trước, một điểm ngắt trong postOnFail() sẽ cung cấp cho bạn ít thông tin về vị trí gọi postOnFail(). Nhưng hãy xem sự khác biệt khi bật ngăn xếp không đồng bộ:

Trước
Điểm ngắt được đặt trong ví dụ về Gmail mô phỏng mà không có ngăn xếp lệnh gọi không đồng bộ.
Bảng điều khiển ngăn xếp lệnh gọi không bật chế độ không đồng bộ.

Tại đây, bạn có thể thấy postOnFail() được khởi tạo từ lệnh gọi lại AJAX nhưng không có thêm thông tin nào.

Sau khi
Điểm ngắt được đặt trong ví dụ về Gmail mô phỏng với ngăn xếp lệnh gọi không đồng bộ.
Bảng điều khiển ngăn xếp lệnh gọi đã bật chế độ không đồng bộ.

Tại đây, bạn có thể thấy rằng XHR được bắt đầu từ submitHandler(). Tuyệt vời!

Khi bật ngăn xếp lệnh gọi không đồng bộ, bạn có thể xem toàn bộ ngăn xếp lệnh gọi để dễ dàng xem liệu yêu cầu có được bắt đầu từ submitHandler() (xảy ra sau khi nhấp vào nút gửi) hay từ retrySubmit() (xảy ra sau khi trễ setTimeout()):

submitHandler()
Điểm ngắt được đặt trong ví dụ về Gmail mô phỏng với ngăn xếp lệnh gọi không đồng bộ
retrySubmit()
Một điểm ngắt khác được đặt trong ví dụ về Gmail mô phỏng với ngăn xếp lệnh gọi không đồng bộ

Xem biểu thức không đồng bộ

Khi bạn đi qua ngăn xếp lệnh gọi đầy đủ, biểu thức được theo dõi cũng sẽ cập nhật để phản ánh trạng thái của biểu thức đó tại thời điểm đó!

Ví dụ về cách sử dụng biểu thức theo dõi với ngăn xếp lệnh gọi không đồng bộ

Đánh giá mã từ các phạm vi trước

Ngoài việc chỉ xem biểu thức, bạn có thể tương tác với mã của mình từ các phạm vi trước đó ngay trong bảng điều khiển JavaScript của DevTools.

Hãy tưởng tượng bạn là Tiến sĩ Who và bạn cần một chút trợ giúp để so sánh đồng hồ từ trước khi bạn lên Tardis cho đến "bây giờ". Trên bảng điều khiển DevTools, bạn có thể dễ dàng đánh giá, lưu trữ và tính toán các giá trị từ nhiều điểm thực thi.

Ví dụ về cách sử dụng bảng điều khiển JavaScript với ngăn xếp lệnh gọi không đồng bộ.
Hãy sử dụng bảng điều khiển JavaScript cùng với ngăn xếp lệnh gọi không đồng bộ để gỡ lỗi mã. Bạn có thể xem bản minh hoạ ở trên tại đây.

Việc ở lại trong DevTools để thao tác với biểu thức sẽ giúp bạn tiết kiệm thời gian không phải chuyển về mã nguồn, chỉnh sửa và làm mới trình duyệt.

Giải quyết các giải pháp hứa hẹn theo chuỗi

Nếu bạn cho rằng luồng Gmail mô phỏng trước đó khó được giải mã nếu không bật tính năng ngăn xếp lệnh gọi không đồng bộ, thì bạn có thể tưởng tượng được việc giải mã sẽ khó khăn hơn bao nhiêu với các luồng không đồng bộ phức tạp hơn như các lời hứa theo chuỗi? Hãy cùng xem lại ví dụ cuối cùng trong hướng dẫn của Jake Archibald về Lời hứa JavaScript.

Dưới đây là một ảnh động nhỏ về cách đi qua ngăn xếp lệnh gọi trong ví dụ async-best-example.html của Jake.

Trước
Điểm ngắt được đặt trong ví dụ về lời hứa không có ngăn xếp lệnh gọi không đồng bộ
Bảng điều khiển ngăn xếp lệnh gọi không bật chế độ không đồng bộ.

Hãy lưu ý cách bảng điều khiển ngăn xếp lệnh gọi khá thiếu thông tin khi cố gắng gỡ lỗi các lời hứa.

Sau khi
Điểm ngắt được đặt trong ví dụ về lời hứa với ngăn xếp lệnh gọi không đồng bộ.
Bảng điều khiển ngăn xếp lệnh gọi đã bật chế độ không đồng bộ.

Rất ấn tượng! Những lời hứa như vậy. Quá nhiều lệnh gọi lại.

Nhận thông tin chi tiết về ảnh động trên web

Hãy cùng tìm hiểu sâu hơn về kho lưu trữ HTML5Rocks. Bạn còn nhớ bài viết Ảnh động gọn nhẹ, mạnh mẽ và nhanh hơn với requestAnimationFrame của Paul Lewis không?

Mở bản minh hoạ requestAnimationFrame và thêm một điểm ngắt ở đầu phương thức update() (khoảng dòng 874) của post.html. Với ngăn xếp lệnh gọi không đồng bộ, chúng ta có được nhiều thông tin chi tiết hơn về requestAnimationFrame, bao gồm cả khả năng quay lại lệnh gọi lại sự kiện cuộn ban đầu.

Trước
Điểm ngắt được đặt trong ví dụ requestAnimationFrame không có ngăn xếp lệnh gọi không đồng bộ.
Bảng điều khiển ngăn xếp lệnh gọi không bật chế độ không đồng bộ.
Sau khi
Điểm ngắt được đặt trong ví dụ requestAnimationFrame với ngăn xếp lệnh gọi không đồng bộ
bật chế độ không đồng bộ.

Theo dõi các bản cập nhật DOM khi sử dụng MutationObserver

MutationObserver cho phép chúng ta quan sát các thay đổi trong DOM. Trong ví dụ đơn giản này, khi bạn nhấp vào nút, một nút DOM mới sẽ được thêm vào <div class="rows"></div>.

Thêm điểm ngắt trong nodeAdded() (dòng 31) trong demo.html. Khi bật ngăn xếp lệnh gọi không đồng bộ, giờ đây, bạn có thể quay lại ngăn xếp lệnh gọi thông qua addNode() đến sự kiện nhấp ban đầu.

Trước
Điểm ngắt được đặt trong ví dụ về mutationObserver mà không có ngăn xếp lệnh gọi không đồng bộ.
Bảng điều khiển ngăn xếp lệnh gọi không bật chế độ không đồng bộ.
Sau khi
Điểm ngắt được đặt trong ví dụ về mutationObserver với ngăn xếp lệnh gọi không đồng bộ.
bật chế độ không đồng bộ.

Mẹo gỡ lỗi JavaScript trong ngăn xếp lệnh gọi không đồng bộ

Đặt tên cho hàm

Nếu thường gán tất cả lệnh gọi lại dưới dạng hàm ẩn danh, bạn nên đặt tên cho các lệnh gọi lại đó để dễ dàng xem ngăn xếp lệnh gọi hơn.

Ví dụ: lấy một hàm ẩn danh như sau:

window.addEventListener('load', function() {
  // do something
});

Và đặt tên cho lớp này như windowLoaded():

window.addEventListener('load', function <strong>windowLoaded</strong>(){
  // do something
});

Khi sự kiện tải kích hoạt, sự kiện này sẽ xuất hiện trong dấu vết ngăn xếp của DevTools với tên hàm thay vì "(hàm ẩn danh)" khó hiểu. Nhờ đó, bạn có thể dễ dàng xem nhanh những gì đang diễn ra trong dấu vết ngăn xếp.

Trước
Một hàm ẩn danh.
Sau
Hàm được đặt tên

Khám phá thêm

Tóm lại, đây là tất cả các lệnh gọi lại không đồng bộ mà DevTools sẽ hiển thị ngăn xếp lệnh gọi đầy đủ:

  • Bộ hẹn giờ: Quay lại vị trí khởi chạy setTimeout() hoặc setInterval().
  • XHR: Quay lại vị trí gọi xhr.send().
  • Khung ảnh động: Quay lại vị trí gọi requestAnimationFrame.
  • Lời hứa: Quay lại nơi lời hứa đã được giải quyết.
  • Object.observe: Quay lại vị trí ban đầu liên kết lệnh gọi lại của trình quan sát.
  • MutationObservers: Quay lại vị trí đã kích hoạt sự kiện trình quan sát đột biến.
  • window.postMessage(): Xem qua các lệnh gọi nhắn tin trong quy trình.
  • DataTransferItem.getAsString()
  • FileSystem API
  • IndexedDB
  • WebSQL
  • Sự kiện DOM đủ điều kiện thông qua addEventListener(): Quay lại vị trí sự kiện được kích hoạt. Do lý do về hiệu suất, không phải sự kiện DOM nào cũng đủ điều kiện sử dụng tính năng ngăn xếp lệnh gọi không đồng bộ. Ví dụ về các sự kiện hiện có: "scroll", "hashchange" và "selectionchange".
  • Sự kiện đa phương tiện thông qua addEventListener(): Quay lại vị trí sự kiện được kích hoạt. Các sự kiện đa phương tiện hiện có bao gồm: sự kiện âm thanh và video (ví dụ: "play", "pause", "ratechange"), sự kiện WebRTC MediaStreamTrackList (ví dụ: "addtrack", "removetrack") và sự kiện MediaSource (ví dụ: "sourceopen").

Việc có thể xem toàn bộ dấu vết ngăn xếp của lệnh gọi lại JavaScript sẽ giúp bạn giải quyết được vấn đề này. Tính năng này trong DevTools sẽ đặc biệt hữu ích khi nhiều sự kiện không đồng bộ xảy ra liên quan đến nhau hoặc nếu một ngoại lệ chưa phát hiện được được gửi từ trong lệnh gọi lại không đồng bộ.

Hãy dùng thử trong Chrome. Nếu bạn muốn phản hồi về tính năng mới này, hãy gửi thư cho chúng tôi trên công cụ theo dõi lỗi của Công cụ của Chrome cho nhà phát triển hoặc trong Nhóm Công cụ của Chrome cho nhà phát triển.