Bộ nhớ hiệu suất cao cho ứng dụng: Storage Foundation API

Nền tảng web ngày càng cung cấp cho nhà phát triển các công cụ cần thiết để xây dựng các ứng dụng có hiệu suất cao và được tinh chỉnh cho web. Đáng chú ý nhất là WebAssembly (Wasm) đã mở ra cánh cửa cho các ứng dụng web nhanh và mạnh mẽ, trong khi các công nghệ như Emscripten hiện cho phép nhà phát triển sử dụng lại mã đã thử nghiệm trên web. Để khai thác tối đa tiềm năng này, nhà phát triển phải có được sức mạnh và tính linh hoạt tương tự khi nói đến bộ nhớ.

Đây là lúc Storage Foundation API phát huy tác dụng. Storage Foundation API là một API bộ nhớ mới, nhanh và không có ý kiến giúp mở ra các trường hợp sử dụng mới và được yêu cầu nhiều cho web, chẳng hạn như triển khai cơ sở dữ liệu hiệu suất cao và quản lý linh hoạt các tệp tạm thời có kích thước lớn. Với giao diện mới này, nhà phát triển có thể "mang bộ nhớ của riêng họ" lên web, giảm khoảng cách về tính năng giữa web và mã dành riêng cho nền tảng.

API Cơ sở lưu trữ được thiết kế giống như một hệ thống tệp rất cơ bản, nhờ đó mang lại cho nhà phát triển tính linh hoạt bằng cách cung cấp các thành phần gốc chung, đơn giản và hiệu quả mà họ có thể xây dựng các thành phần cấp cao hơn. Các ứng dụng có thể tận dụng công cụ phù hợp nhất với nhu cầu của mình, tìm ra sự cân bằng giữa khả năng hữu dụng, hiệu suất và độ tin cậy.

Tại sao web cần một API bộ nhớ khác?

Nền tảng web cung cấp một số tuỳ chọn bộ nhớ cho nhà phát triển, mỗi tuỳ chọn được xây dựng theo các trường hợp sử dụng cụ thể.

  • Một số tuỳ chọn trong số này rõ ràng không trùng lặp với đề xuất này vì chúng chỉ cho phép lưu trữ một lượng dữ liệu rất nhỏ, chẳng hạn như cookie hoặc API Bộ nhớ web bao gồm cơ chế sessionStoragelocalStorage.
  • Các tuỳ chọn khác không còn được dùng nữa vì nhiều lý do, chẳng hạn như File and Directory Entries API (API mục nhập tệp và thư mục) hoặc WebSQL.
  • File System Access API (API truy cập hệ thống tệp) có giao diện API tương tự, nhưng mục đích sử dụng là giao tiếp với hệ thống tệp của ứng dụng và cung cấp quyền truy cập vào dữ liệu có thể nằm ngoài quyền sở hữu của nguồn gốc hoặc thậm chí là trình duyệt. Mục tiêu khác biệt này đi kèm với các cân nhắc về bảo mật nghiêm ngặt hơn và chi phí hiệu suất cao hơn.
  • Bạn có thể sử dụng API IndexedDB làm phần phụ trợ cho một số trường hợp sử dụng của API Cơ sở lưu trữ. Ví dụ: Emscripten bao gồm IDBFS, một hệ thống tệp ổn định dựa trên IndexedDB. Tuy nhiên, vì IndexedDB về cơ bản là một kho khoá-giá trị, nên nó có các giới hạn hiệu suất đáng kể. Hơn nữa, việc truy cập trực tiếp vào các tiểu mục của một tệp còn khó khăn và chậm hơn trong IndexedDB.
  • Cuối cùng, giao diện CacheStorage được hỗ trợ rộng rãi và được điều chỉnh để lưu trữ dữ liệu có kích thước lớn như tài nguyên ứng dụng web, nhưng các giá trị này không thể thay đổi.

API Cơ sở lưu trữ là một nỗ lực nhằm lấp đầy mọi khoảng trống của các tuỳ chọn lưu trữ trước đó bằng cách cho phép lưu trữ hiệu quả các tệp lớn có thể thay đổi được xác định trong nguồn gốc của ứng dụng.

Các trường hợp sử dụng đề xuất cho API Cơ sở lưu trữ

Sau đây là ví dụ về các trang web có thể sử dụng API này:

  • Ứng dụng sáng tạo hoặc cải thiện năng suất hoạt động trên một lượng lớn dữ liệu video, âm thanh hoặc hình ảnh. Các ứng dụng như vậy có thể giảm tải các phân đoạn xuống ổ đĩa thay vì giữ các phân đoạn đó trong bộ nhớ.
  • Các ứng dụng dựa vào hệ thống tệp ổn định có thể truy cập được từ Wasm và cần hiệu suất cao hơn so với những gì IDBFS có thể đảm bảo.

Storage Foundation API là gì?

API này có hai phần chính:

  • Lệnh gọi hệ thống tệp cung cấp chức năng cơ bản để tương tác với các tệp và đường dẫn tệp.
  • Trình xử lý tệp, cung cấp quyền đọc và ghi vào một tệp hiện có.

Lệnh gọi hệ thống tệp

API Cơ sở lưu trữ giới thiệu một đối tượng mới, storageFoundation, nằm trên đối tượng window và bao gồm một số hàm:

  • storageFoundation.open(name): Mở tệp có tên đã cho nếu tệp đó tồn tại, nếu không thì tạo một tệp mới. Trả về một lời hứa phân giải với tệp đã mở.
  • storageFoundation.delete(name): Xoá tệp có tên đã cho. Trả về một lời hứa sẽ giải quyết khi tệp bị xoá.
  • storageFoundation.rename(oldName, newName): Đổi tên tệp từ tên cũ thành tên mới một cách nguyên vẹn. Trả về một lời hứa sẽ giải quyết khi tệp được đổi tên.
  • storageFoundation.getAll(): Trả về một lời hứa phân giải bằng một mảng gồm tất cả tên tệp hiện có.
  • storageFoundation.requestCapacity(requestedCapacity): Yêu cầu dung lượng mới (tính bằng byte) để ngữ cảnh thực thi hiện tại sử dụng. Trả về một lời hứa đã được phân giải với dung lượng còn lại.
  • storageFoundation.releaseCapacity(toBeReleasedCapacity): Giải phóng số byte đã chỉ định từ ngữ cảnh thực thi hiện tại và trả về một lời hứa phân giải với dung lượng còn lại.
  • storageFoundation.getRemainingCapacity(): Trả về một lời hứa phân giải với dung lượng có sẵn cho ngữ cảnh thực thi hiện tại.

Tên tệp

Bạn có thể làm việc với tệp thông qua các hàm sau:

  • NativeIOFile.close(): Đóng một tệp và trả về một lời hứa sẽ giải quyết khi thao tác hoàn tất.
  • NativeIOFile.flush(): Đồng bộ hoá (tức là xoá sạch) trạng thái của tệp trong bộ nhớ với thiết bị lưu trữ và trả về một lời hứa sẽ giải quyết khi thao tác hoàn tất.
  • NativeIOFile.getLength(): Trả về một lời hứa phân giải với độ dài của tệp tính bằng byte.
  • NativeIOFile.setLength(length): Đặt độ dài của tệp tính bằng byte và trả về một lời hứa sẽ giải quyết khi thao tác hoàn tất. Nếu độ dài mới nhỏ hơn độ dài hiện tại, các byte sẽ bị xoá bắt đầu từ cuối tệp. Nếu không, tệp sẽ được mở rộng bằng các byte có giá trị bằng 0.
  • NativeIOFile.read(buffer, offset): Đọc nội dung của tệp tại độ dời nhất định thông qua một vùng đệm là kết quả của việc chuyển vùng đệm nhất định, sau đó vùng đệm này sẽ được tách rời. Trả về một NativeIOReadResult có vùng đệm đã chuyển và số byte đã đọc thành công.

    NativeIOReadResult là một đối tượng bao gồm hai mục:

    • buffer: ArrayBufferView, là kết quả của việc chuyển vùng đệm được truyền đến read(). Nó có cùng loại và chiều dài với vùng đệm nguồn.
    • readBytes: Số byte đã đọc thành công vào buffer. Kích thước này có thể nhỏ hơn kích thước bộ nhớ đệm nếu xảy ra lỗi hoặc nếu phạm vi đọc vượt quá cuối tệp. Giá trị này được đặt thành 0 nếu phạm vi đọc nằm ngoài cuối tệp.
  • NativeIOFile.write(buffer, offset): Ghi nội dung của vùng đệm đã cho vào tệp tại độ dời đã cho. Vùng đệm được chuyển trước khi ghi bất kỳ dữ liệu nào và do đó được tách rời. Trả về một NativeIOWriteResult có vùng đệm đã chuyển và số byte đã ghi thành công. Tệp sẽ được mở rộng nếu phạm vi ghi vượt quá chiều dài của tệp.

    NativeIOWriteResult là một đối tượng bao gồm hai mục:

    • buffer: ArrayBufferView là kết quả của việc chuyển vùng đệm được truyền đến write(). Nó có cùng loại và chiều dài với vùng đệm nguồn.
    • writtenBytes: Số byte đã được ghi thành công vào buffer. Kích thước này có thể nhỏ hơn kích thước bộ đệm nếu xảy ra lỗi.

Ví dụ hoàn chỉnh

Để làm rõ hơn các khái niệm được giới thiệu ở trên, sau đây là hai ví dụ hoàn chỉnh hướng dẫn bạn qua các giai đoạn trong vòng đời của tệp Cơ sở lưu trữ.

Mở, ghi, đọc, đóng

// Open a file (creating it if needed).
const file = await storageFoundation.open('test_file');
try {
  // Request 100 bytes of capacity for this context.
  await storageFoundation.requestCapacity(100);

  const writeBuffer = new Uint8Array([64, 65, 66]);
  // Write the buffer at offset 0. After this operation, `result.buffer`
  // contains the transferred buffer and `result.writtenBytes` is 3,
  // the number of bytes written. `writeBuffer` is left detached.
  let result = await file.write(writeBuffer, 0);

  const readBuffer = new Uint8Array(3);
  // Read at offset 1. `result.buffer` contains the transferred buffer,
  // `result.readBytes` is 2, the number of bytes read. `readBuffer` is left
  // detached.
  result = await file.read(readBuffer, 1);
  // `Uint8Array(3) [65, 66, 0]`
  console.log(result.buffer);
} finally {
  file.close();
}

Mở, đăng, xoá

// Open three different files (creating them if needed).
await storageFoundation.open('sunrise');
await storageFoundation.open('noon');
await storageFoundation.open('sunset');
// List all existing files.
// `["sunset", "sunrise", "noon"]`
await storageFoundation.getAll();
// Delete one of the three files.
await storageFoundation.delete('noon');
// List all remaining existing files.
// `["sunrise", "noon"]`
await storageFoundation.getAll();

Bản minh hoạ

Bạn có thể chơi với bản minh hoạ Storage Foundation API trong phần nhúng bên dưới. Tạo, đổi tên, ghi vào và đọc từ các tệp, đồng thời xem dung lượng còn trống mà bạn đã yêu cầu cập nhật khi thực hiện các thay đổi. Bạn có thể tìm thấy mã nguồn của bản minh hoạ trên Glitch.

Tính bảo mật và quyền truy cập

Nhóm Chromium đã thiết kế và triển khai API Cơ sở lưu trữ bằng các nguyên tắc cốt lõi được xác định trong bài viết Kiểm soát quyền truy cập vào các tính năng mạnh mẽ của nền tảng web, bao gồm cả quyền kiểm soát của người dùng, tính minh bạch và tính công thái học.

Tương tự như các API bộ nhớ hiện đại khác trên web, quyền truy cập vào API Cơ sở lưu trữ bị ràng buộc theo nguồn gốc, nghĩa là một nguồn gốc chỉ có thể truy cập vào dữ liệu do chính nó tạo. Phương thức này cũng chỉ giới hạn ở các ngữ cảnh bảo mật.

Quyền kiểm soát của người dùng

Hạn mức bộ nhớ sẽ được dùng để phân phối quyền truy cập vào dung lượng ổ đĩa và ngăn chặn hành vi sai trái. Trước tiên, bạn cần yêu cầu bộ nhớ mà bạn muốn chiếm dụng. Giống như các API bộ nhớ khác, người dùng có thể xoá dung lượng mà API Cơ sở bộ nhớ chiếm thông qua trình duyệt.

Đường liên kết hữu ích

Lời cảm ơn

API Cơ sở lưu trữ do Emanuel KrivoyRichard Stotz chỉ định và triển khai. Bài viết này đã được Pete LePageJoe Medley xem xét.

Hình ảnh chính qua Markus Spiske trên Unsplash.