Truy xuất mảng trong JavaScript một cách an toàn

Gần đây mới biết rằng trong JavaScript, khi truy xuất một phần tử trong mảng sau khi kiểm tra trước độ dài của mảng, thì vẫn không đảm bảo được rằng phần tử đó tồn tại.

Ví dụ trong đoạn code 1, biến "first" tại dòng 3 có thể bị undefined, dẫn đến "tz" tại dòng 4 cũng bị undefined theo. Lý do là mảng trong JS là mảng thưa, cho phép các vị trí phần tử không liên tục. Tức là có thể có phần tử ở vị trí 1 mà không có ở vị trí 0.

function useFirst(items: Date[]) {
  if (items.length) {
    const first = items[0]
    const tz = first.getTimezoneOffset()
    // Do something more
  }
}

Trong các ứng dụng JavaScipt, bug mà một biến nào đó bỗng nhiên bị undefined, và lây nhiễm undefined từ một dòng code xa tít tắp trước đó, là một lỗi hay gặp và khó truy gốc, nên người ta thường dùng TypeScript để kiểm tra chặt, phát hiện sớm. Tuy nhiên, trong trường hợp truy xuất mảng thế này, TypeScript không phát hiện bug tiềm tàng ở dòng 3, nó vẫn cho rằng first luôn luôn xác định, thuộc kiểu Date.

Thực ra thì đó là cấu hình mặc định của TypeScript, nếu muốn nó kiểm tra trường hợp này thì chỉ cần bật option noUncheckedIndexedAccess lên. Nhớ rằng option này có bật thì dòng if (items.length) cũng vẫn vô dụng, bạn sẽ phải sửa lại tất cả những đoạn code nào viết như vậy. Vì việc kiểm tra, so sánh với undefined mệt mỏi quá nên mình thường hay dùng ts-belt, như đoạn code 2 và 3.

import { A, O } from '@mobily/ts-belt'

function useFirst(items: Date[]) {
  O.tap(A.head(items), d => {
    const tz = d.getTimezoneOffset()
    // Do something more
  }
}
import { A, O, pipe } from '@mobily/ts-belt'

function useFirst(items: Date[]) {
  pipe(
    items,
    A.head,
    O.tap(d => {
      const tz = d.getTimezoneOffset()
      // Do something more
    })
  )
}

ở đây, A.head(items) sẽ luôn trả về Option<Date>, và O.tap được dùng để gọi một callback khi Option<Date> có giá trị, và khi đó thì biến d sẽ có kiểu cụ thể là Date rồi.

Thư viện này giúp viết code theo kiểu functional programming. Thư viện này cũng định nghĩa kiểu Option, Result gần giống Rust để làm việc dễ hơn. Tất nhiên vì JS / TS không phải là một ngôn ngữ functional programming nên việc dùng kiểu Option / Result kia cũng khá dài dòng.