Trình duyệt cho phép chúng ta theo dõi việc tải các tài nguyên bên ngoài – tập lệnh, framework nội tuyến, hình ảnh, v.v. Và hôm nay cafedev chia sẻ về nó.

Có hai sự kiện cho nó:

  • onload – tải thành công,
  • onerror – một lỗi xảy ra.

1. Đang tải tập lệnh

Giả sử chúng ta cần tải một tập lệnh của bên thứ ba và gọi một hàm nằm ở đó.

Chúng ta có thể tải nó động, như thế này:

let script = document.createElement('script');
script.src = "my.js";

document.head.append(script);

… Nhưng làm thế nào để chạy hàm được khai báo bên trong tập lệnh đó? Chúng ta cần đợi cho đến khi tập lệnh tải và chỉ sau đó chúng ta mới có thể gọi nó.

Xin lưu ý:

Đối với các tập lệnh của riêng mình, chúng ta có thể sử dụng các mô-đun JavaScript ở đây, nhưng chúng không được các thư viện bên thứ ba chấp nhận rộng rãi.

1.1. script.onload

Người trợ giúp chính là sự kiện load. Nó kích hoạt sau khi tập lệnh được tải và thực thi.

Ví dụ:

let script = document.createElement('script');

// can load any script, from any domain
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);

script.onload = function() {
  // the script creates a helper function "_"
  alert(_); // the function is available
};

Vì vậy, trong chúng ta onload có thể sử dụng các biến script, chạy các hàm, v.v.

… Và nếu tải không thành công thì sao? Ví dụ: không có tập lệnh nào như vậy (lỗi 404) hoặc máy chủ không hoạt động (không khả dụng).

1.2. script.onerror

Các lỗi xảy ra trong quá trình tải tập lệnh có thể được theo dõi trong một sự kiện error.

Ví dụ: hãy yêu cầu một tập lệnh không tồn tại:

let script = document.createElement('script');
script.src = "https://example.com/404.js"; // no such script
document.head.append(script);

script.onerror = function() {
  alert("Error loading " + this.src); // Error loading https://example.com/404.js
};

Xin lưu ý rằng chúng ta không thể lấy chi tiết lỗi HTTP ở đây. Chúng ta không biết liệu đó có phải là lỗi 404 hay 500 hay gì khác hay không. Chỉ là tải không thành công.

Quan trọng:

Sự kiện onload/ onerror chỉ theo dõi quá trình tải.

Các lỗi có thể xảy ra trong quá trình xử lý và thực thi tập lệnh nằm ngoài phạm vi của các sự kiện này. Đó là: nếu một tập lệnh được tải thành công, thì onload sẽ kích hoạt, ngay cả khi nó có lỗi lập trình trong đó. Để theo dõi lỗi tập lệnh, người ta có thể sử dụng trình xử lý window.onerror toàn cục.

2. Các nguồn lực khác

Sự kiện load và error cũng hoạt động đối với các tài nguyên khác, về cơ bản đối với bất kỳ tài nguyên nào có bên ngoài src.

Ví dụ:

let img = document.createElement('img');
img.src = "https://js.cx/clipart/train.gif"; // (*)

img.onload = function() {
  alert(`Image loaded, size ${img.width}x${img.height}`);
};

img.onerror = function() {
  alert("Error occurred while loading image");
};

Tuy nhiên, có một số lưu ý:

  • Hầu hết các tài nguyên bắt đầu tải khi chúng được thêm vào tài liệu. Nhưng <img> là một ngoại lệ. Nó bắt đầu tải khi nhận được một src (*).
  • Đối với <iframe>, sự kiện iframe.onload sẽ kích hoạt khi quá trình tải iframe kết thúc, cả khi tải thành công và trong trường hợp có lỗi.

Đó là vì lý do lịch sử.

3. Chính sách về Crossorigin

Có một quy tắc: tập lệnh từ một trang web không thể truy cập nội dung của trang khác. Vì vậy, ví dụ: một tập lệnh tại https://facebook.com không thể đọc hộp thư của người dùng tại https://gmail.com .

Hay nói chính xác hơn, một nguồn gốc (bộ ba miền / cổng / giao thức) không thể truy cập nội dung từ một nguồn khác. Vì vậy, ngay cả khi chúng ta có một tên miền phụ, hoặc chỉ một cổng khác, đây là những nguồn gốc khác nhau và không có quyền truy cập vào nhau.

Quy tắc này cũng ảnh hưởng đến tài nguyên từ các miền khác.

Nếu chúng ta đang sử dụng tập lệnh từ một miền khác và có lỗi trong đó, chúng ta không thể nhận chi tiết lỗi.

Ví dụ: hãy lấy một tập lệnh error.js bao gồm một lệnh gọi hàm duy nhất (không hợp lệ):

// 📁 error.js

// 📁 error.js
noSuchFunction();

Bây giờ tải nó từ cùng một trang web mà nó được đặt:

<script>
window.onerror = function(message, url, line, col, errorObj) {
  alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="/article/onload-onerror/crossorigin/error.js"></script>

Chúng ta có thể thấy một báo cáo lỗi tốt, như sau:

<!-- wp:paragraph -->
<p>Uncaught ReferenceError: noSuchFunction is not defined</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1</p>
<!-- /wp:paragraph -->

Bây giờ, hãy tải cùng một tập lệnh từ một miền khác:

<script>
window.onerror = function(message, url, line, col, errorObj) {
  alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>

Báo cáo khác, như thế này:

Script error.
, 0:0

Thông tin chi tiết có thể khác nhau tùy thuộc vào trình duyệt, nhưng ý tưởng thì giống nhau: mọi thông tin về phần bên trong của tập lệnh, bao gồm cả dấu vết ngăn xếp lỗi, đều bị ẩn. Chính xác vì nó đến từ miền khác.

Tại sao chúng ta cần chi tiết lỗi?

Có rất nhiều dịch vụ (và chúng ta có thể xây dựng dịch vụ của riêng mình) lắng nghe các lỗi chung khi sử dụng window.onerror, lưu lỗi và cung cấp giao diện để truy cập và phân tích chúng. Điều đó thật tuyệt, vì chúng ta có thể thấy lỗi thực sự do người dùng của chúng ta kích hoạt. Nhưng nếu một tập lệnh đến từ một nguồn gốc khác, thì không có nhiều thông tin về các lỗi trong đó, như chúng ta vừa thấy.

Chính sách nguồn gốc chéo tương tự (CORS) cũng được thực thi đối với các loại tài nguyên khác.

Để cho phép truy cập nguồn gốc chéo, thẻ < script > cần phải có thuộc tính crossorigin, ngoài ra máy chủ từ xa phải cung cấp các tiêu đề đặc biệt.

Có ba cấp độ truy cập nguồn gốc chéo:

  1. Không có thuộc tính crossorigin – truy cập bị cấm.
  2. crossorigin=”anonymous”– được phép truy cập nếu máy chủ phản hồi với tiêu đề Access-Control-Allow-Origin có * hoặc nguồn gốc của chúng ta. Trình duyệt không gửi thông tin ủy quyền và cookie đến máy chủ từ xa.
  3. crossorigin=”use-credentials”– được phép truy cập nếu máy chủ gửi lại tiêu đề Access-Control-Allow-Origin với nguồn gốc của chúng ta và Access-Control-Allow-Credentials: true. Trình duyệt gửi thông tin ủy quyền và cookie đến máy chủ từ xa.

Xin lưu ý:

Bạn có thể đọc thêm về truy cập nguồn gốc chéo trong chương Tìm nạp: Yêu cầu nguồn gốc . Nó mô tả phương thức fetch cho các yêu cầu mạng, nhưng chính sách hoàn toàn giống nhau.

Những thứ như “cookie” không thuộc phạm vi hiện tại của chúng ta, nhưng bạn có thể đọc về chúng trong chương Cookies, document.cookie .

Trong trường hợp của chúng ta, chúng ta không có bất kỳ thuộc tính crossorigin nào. Vì vậy, truy cập nguồn gốc chéo đã bị cấm. Hãy thêm nó vào.

Chúng ta có thể chọn giữa “anonymous”(không gửi cookie, cần một tiêu đề phía máy chủ) và “use-credentials”(cũng gửi cookie, cần hai tiêu đề phía máy chủ).

Nếu chúng ta không quan tâm đến cookie, thì đây “anonymous” là cách để đi:

<script>
window.onerror = function(message, url, line, col, errorObj) {
  alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script crossorigin="anonymous" src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>

Bây giờ, giả sử rằng máy chủ cung cấp tiêu đề Access-Control-Allow-Origin, mọi thứ đều ổn. Chúng ta có báo cáo lỗi đầy đủ.

4. Tóm lược

Hình ảnh <img>, kiểu bên ngoài, tập lệnh và các tài nguyên khác cung cấp load và error các sự kiện để theo dõi tải của chúng:

  • load kích hoạt khi tải thành công,
  • error kích hoạt khi tải không thành công.

Ngoại lệ duy nhất là <iframe>: vì lý do lịch sử, nó luôn kích hoạt load, cho bất kỳ quá trình hoàn thành tải nào, ngay cả khi không tìm thấy trang.

Sự kiện readystatechange cũng hoạt động cho các tài nguyên, nhưng hiếm khi được sử dụng, vì các sự kiện load/error đơn giản hơn.

Nguồn và tài liệu tiếng anh tham khảo:

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!