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. API Cơ sở lưu trữ là một API lưu trữ 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.

Storage Foundation API được thiết kế giống với một hệ thống tệp rất cơ bản. Vì vậy, API này giúp nhà phát triển linh hoạt bằng cách cung cấp các dữ liệu gốc chung, đơn giản và hiệu quả để 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ụ tốt nhất để đáp ứng nhu cầu của ứng dụng, tìm được sự cân bằng hợp lý 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 hiện không còn được dùng nữa vì nhiều lý do như File and Directory Bundles 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ì về cơ bản IndexedDB là một kho khoá-giá trị, nên kho này có các giới hạn đáng kể về hiệu suất. 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, chẳng hạn như tài nguyên ứng dụng web, nhưng các giá trị là không thể thay đổi.

API Nền tảng lưu trữ là một nỗ lực nhằm khắc phục tất cả các 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 nên dùng cho Storage Foundation API

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 mức mà IDBFS có thể đảm bảo.

Storage Foundation API là gì?

Có hai phần chính của API:

  • 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ề lời hứa phân giải bằng một mảng gồm tất cả cá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 hiện có cho ngữ cảnh thực thi hiện tại.

Tên người dùng trong 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á) trạng thái trong bộ nhớ của tệp với thiết bị lưu trữ và trả về lời hứa sẽ phân giải 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): Thiết lập độ dài của tệp tính bằng byte và trả về một lời hứa sẽ phân giải khi thao tác hoàn tất. Nếu độ dài mới nhỏ hơn độ dài hiện tại, thì các byte sẽ bị xoá kể 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 đọ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 nhất định vào tệp ở độ lệch nhất định. 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. Dung lượng này có thể nhỏ hơn dung lượng bộ nhớ đệm nếu xảy ra lỗi.

Ví dụ đầy đủ

Để 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ở, liệt kê, 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ể xem bản minh hoạ API Storage Foundation 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.

Theo cùng một mẫu với các API lưu trữ hiện đại khác trên web, quyền truy cập vào Storage Foundation API sẽ có 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 tự 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 lưu trữ khác, người dùng có thể xoá dung lượng do Storage Foundation API chiếm dụng thông qua trình duyệt của họ.

Đườ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 do Pete LePageJoe Medley đánh giá.

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