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

Pearl Chen

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 các lệnh gọi lại không đồng bộ cho phép bạn viết mã dựa trên sự kiện, nhưng cũng khiến việc theo dõi lỗi xảy ra với trải nghiệm kéo tóc vì JavaScript không thực thi theo kiểu tuyến tính.

Thật may là giờ đây trong Công cụ của Chrome cho nhà phát triển, 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 ngắn gọn về ngăn xếp lệnh gọi không đồng bộ.
Thông tin tổng quan ngắn gọn về ngăn xếp lệnh gọi không đồng bộ. (Chúng tôi sẽ sớm phân tích luồng 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ể xem chi tiết trạng thái của ứng dụng web tại nhiều thời điểm tại nhiều thời điểm. Xem 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à nhiều trình nghe khác.

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 mọi biến tại thời điểm thực thi trong thời gian chạy cụ thể đó. Nó 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 vài trường hợp sau.

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

Hãy bật tính năng mới này trong Chrome để dùng thử. Chuyển đến bảng Nguồn của Công cụ cho nhà phát triển của Chrome Canary.

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 chế độ "Async" (Không đồng bộ). Chọn hộp đánh dấu để bật hoặc tắt tính năng gỡ lỗi không đồng bộ. (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 sự kiện bộ tính giờ bị trì hoãn và phản hồi XHR

Có thể bạn đã thấy tuỳ chọn này trước đây trong Gmail:

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

Nếu có sự cố khi gửi yêu cầu (máy chủ đang 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 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 bộ tính giờ bị trễ và phản hồi XHR, tôi đã tạo lại quy trình đó bằng 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 như sau:

Sơ đồ quy trình 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à điểm chính để tính năng DevTool mới này mang lại lợi ích nhiều nhất vì các phương thức này hoạt động không đồng bộ.

Bằng cách chỉ xem xét ngăn xếp lệnh gọi trong các phiên bản trước của Công cụ cho nhà phát triển, một điểm ngắt trong postOnFail() sẽ cung cấp cho bạn một ít thông tin về nơi gọi postOnFail(). Tuy nhiên, hãy xem sự khác biệt khi bật các ngăn xếp không đồng bộ:

Trước
Điểm ngắt được đặt trong ví dụ mô phỏng Gmail 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ộ.

Ở đây, bạn có thể thấy rằng 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 khác.

Sau
Điểm ngắt được đặt trong ví dụ mô phỏng Gmail với cá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 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 yêu cầu đượ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 độ trễ setTimeout()):

submitHandler()
Ví dụ về điểm ngắt được đặt trong ví dụ mô phỏng Gmail với các 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ụ mô phỏng Gmail với các 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 toàn bộ ngăn xếp lệnh gọi, các biểu thức đã theo dõi cũng sẽ cập nhật để phản ánh trạng thái tại thời điểm đó!

Ví dụ về cách sử dụng biểu thức đồng hồ với ngăn xếp lệnh gọi aysnc

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

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

Hãy tưởng tượng bạn là Tiến sĩ Ai và bạn cần một chút trợ giúp để so sánh đồng hồ từ trước khi vào Tardis với "bây giờ". Từ bảng điều khiển Công cụ cho nhà phát triển, bạn có thể dễ dàng đánh giá, lưu trữ và tính toán các giá trị từ các điểm thực thi khác nhau.

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 aysnc.
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ã của bạn. Bạn có thể xem bản minh hoạ ở trên tại đây.

Việc sử dụng Công cụ cho nhà phát triển để 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 lời hứa được xâu chuỗi

Nếu bạn cho rằng quy trình Gmail mô phỏng trước đó sẽ khó làm sáng tỏ 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 rằng 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 không? 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 với JavaScript.

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

Trước
Ví dụ về điểm ngắt được đặt trong hứa hẹn 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ộ.

Hãy lưu ý rằng bảng điều khiển Call Stack (Ngăn xếp lệnh gọi) có rất ít thông tin khi cố gắng gỡ lỗi cho các lời hứa.

Sau
Ví dụ về điểm ngắt được đặt trong hứa hẹn với cá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 bật chế độ không đồng bộ.

Rất ấn tượng! Đó là những lời hứa hẹn tuyệt vời. 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 kỹ hơn về kho lưu trữ HTML5Rocks. Bạn có nhớ Ảnh động 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 tôi nhận đượ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 đã khởi tạo.

Trước
Điểm ngắt được đặt trong ví dụ về requestAnimationFrame 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
Điểm ngắt được đặt trong ví dụ về requestAnimationFrame với các ngăn xếp lệnh gọi không đồng bộ
bật chế độ không đồng bộ.

Theo dõi nội dung cập nhật của DOM khi sử dụng MutationObserver

MutationObserver cho phép chúng ta quan sát những 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ộ, bạn hiện có thể đẩy ngăn xếp lệnh gọi quay lại addNode() để đến sự kiện nhấp ban đầu.

Trước
Điểm ngắt được đặt trong ví dụ về mutationObserver 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
Điểm ngắt được đặt trong ví dụ về mutationObserver với các 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 có ý định chỉ định tất cả cá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ụ: dùng một hàm ẩn danh như sau:

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

Và đặt tên như windowLoaded():

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

Khi sự kiện tải kích hoạt, sự kiện đó sẽ xuất hiện trong dấu vết ngăn xếp Công cụ cho nhà phát triển cùng với tên hàm thay vì nội dung khó hiểu "(hàm ẩn danh)". Điều này giúp bạn 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
Một hàm được đặt tên

Khám phá thêm

Tóm lại, dưới đây là tất cả các lệnh gọi lại không đồng bộ mà trong đó Công cụ cho nhà phát triển sẽ hiển thị toàn bộ ngăn xếp lệnh gọi:

  • Hẹn giờ: Quay lại vị trí khởi động setTimeout() hoặc setInterval().
  • XHRs: Quay lại nơi xhr.send() được gọi.
  • Khung ảnh động: Quay lại vị trí gọi requestAnimationFrame.
  • Promise (Lời hứa): Quay lại nơi đã giải quyết lời hứa.
  • Object.observe: Quay lại vị trí ban đầu lệnh gọi lại trình quan sát được liên kế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 quá 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í kích hoạt sự kiện. Vì lý do 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 cuộc gọi không đồng bộ. Ví dụ về các sự kiện hiện có bao gồm: "scroll", "hashchange" và "selectionchange".
  • Sự kiện đa phương tiện qua addEventListener(): Quay lại nơi kích hoạt sự kiện. Các sự kiện đa phương tiện có sẵn bao gồm: sự kiện âm thanh và video (ví dụ: "phát", "tạm dừng", "tỷ lệ thay đổi"), sự kiện WebRTC MediaStreamTrackList (ví dụ: "addtrack", "removetrack") và sự kiện MediaSource (ví dụ: "sourceopen").

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

Hãy dùng thử trong Chrome. Nếu bạn có ý kiến phản hồi về tính năng mới này, hãy gửi phản hồi cho chúng tôi về 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.