Skip to content

Instantly share code, notes, and snippets.

@long-nc
Last active January 4, 2021 03:27
Show Gist options
  • Save long-nc/b6dd5bb654c60b16fc262131fd6a5a45 to your computer and use it in GitHub Desktop.
Save long-nc/b6dd5bb654c60b16fc262131fd6a5a45 to your computer and use it in GitHub Desktop.

Tìm hiểu về Reactive Programming

1. Khái niệm reactive programming

Reactive programming (RP) là một mô hình lập trình dùng để xử lý các sự kiện bất đồng bộ mà theo wikipedia định nghĩa đầy đủ là:

Reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm it is possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow.

Một định nghĩa ngắn gọn nhưng diễn giải đầy đủ đặc tính cũng như mục đích của reactive programming, chúng ta sẽ cùng mổ xẻ nó trong bài viết này:

Đối với RP, mọi thứ đều là data stream (luồng dữ liệu)

Không chỉ có Event Bus hay các sự kiện tương tác từ người dùng, data stream có thể được tạo từ bất cứ thứ gì: variables, user inputs, caches, data structures... Ví dụ, việc load ảnh trên trang web hay sự kiện click vào url đều được coi là stream.

Có 2 điểm quan trọng khi làm việc với stream là:

  1. Mỗi khi có dữ liệu mới, stream sẽ push sự kiện và bạn có thể phản ứng (react) với sự kiện đó.
  2. Việc coi tất cả đối tượng là stream có ưu điểm lớn đó là việc xử lý dữ liệu sẽ nhất quán, và thao tác với stream rất linh hoạt.

Bạn có thể kết hợp (combine), tạo (create) và lọc (filter) bất kỳ stream nào

Một stream có thể dùng làm đầu vào cho stream khác. Thậm chí nhiều stream cũng có thể làm đầu vào cho một stream khác. Các stream có thể được gộp (merge), lọc (filter) và ánh xạ (map) sang một stream khác.

RP là một mô hình lập trình declarative
Bạn đọc có thể xem thêm về imperative programming vs declarative programming tại đây

Trong khi mô hình imperative thuận lợi cho việc suy diễn logic code thì declarative chú trọng vào việc xây dựng kiến trúc sạch (clean) và ít tác dụng phụ (side-effect).

Lấy ví dụ bài toán yêu cầu tính a = b + c, với mô hình imperative mỗi khi b hay c thay đổi ta phải thực hiện việc tính toán lại a để đảm bảo giá trị của a luôn là mới nhất. Trong khi đó, với mô hình declarative, hệ thống chỉ cần biết rằng giá trị của a phụ thuộc vào bc, và phần quyết định khi nào cần tính toán lại a sẽ được xử lý tự động.

Chỉ với việc thay đổi cách tiếp cận khác, trừu tượng hoá vấn đề lên thì ta đã thấy nó đơn giản hơn rất nhiều. RP là mô hình giải quyết việc xử lý bất đồng bộ một cách đơn giản như vậy.

Để thấy rõ hơn sức mạnh của Reactive Programming, ta sẽ xem xét ví dụ sau: Nhận sự kiện click từ phía người dùng và hiển thị xem đó là single click, double click hay multiple click (cần đưa ra được số lần click liên tục).

  1. Hướng tiếp cận của imperative programming có thể như sau: Có 2 vấn đề với cách làm này: cần có biến global và xử lý timeout
    // global state
    let clickCount = 0;
    let timeoutId;

    document.addEventListener("click", () => {
        // khi có sự kiện click, tăng biến đếm
        clickCount += 1;

        // xoá timeout cũ (nếu có), và đặt lại timeout
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            // khi hết 250ms chờ thì in ra số lần click
            console.log(`Clicked ${clickCount} times`);
            // reset lại biến đếm
            clickCount = 0;
        }, 250);
    });
  1. Trong khi đó nếu xử lý với stream ta sẽ có đoạn code rất gọn gàng, không side-effect
    const clicks = fromEvent(document, "click");
    const result = clicks.pipe(
        buffer(clicks.pipe(debounceTime(250))),
        map(list => list.length)
    );
    result.subscribe(clickCount => console.log(`Clicked ${clickCount} times`))

Hai biến global đã được chuyển thành các operator của stream. Đầu tiên ta sử dụng buffer để cộng dồn các click và trả về mảng event click, buffer này chỉ được giải phóng khi lần click cuối cùng đã xảy ra 250ms trước. Sau đó sử dụng map để lấy độ dài mảng, đây chính là số lần click. Cách tiếp cận này loại bỏ hoàn toàn biến global, qua đó loại bỏ side-effect của code.

Ngoài ra, khi sử dụng các thao tác merge, filter, map... stream thì một stream mới sẽ được trả về và không làm thay đổi stream gốc. Tính bất biến (immutability) này cũng làm giảm tỉ lệ side-effect của chương trình.

RP sử dụng observer pattern để đơn giản hoá việc phát và nhận event Một stream có thể emit một trong 3 kết quả: value, error, hoặc tín hiệu 'completed'. Trong đó 'completed' được gửi khi stream ngắt kết nối. Việc nghe ngóng (listen) một stream được gọi là subscribing. Hàm xử lý kết quả được gọi là observer. Còn stream được gọi là các observable.

2. Tại sao nên dùng reactive programming

RP làm code của bạn trừu tượng hoá hơn, khi đó bạn chỉ cần quan tâm tới các business logic mà có thể bỏ qua phần triển khai chi tiết vì nó đã được hệ thống xử lý. Và đương nhiên khi đã giảm số lượng code xuống thì khả năng dính bug của chương trình cũng ít đi.

Các ứng dụng web và mobile nhìn chung hiện giờ đều chú trọng đến tương tác thời gian thực và việc phải xử lý hàng loạt các sự kiện từ người dùng là điều tất yếu. Reactive programming là một giải pháp tốt cho vấn đề này.

3. Giới thiệu về thư viện RxJS

// TO BE CONTINUED

4. Implement tính năng đơn giản bằng Reactive Programming

// TO BE CONTINUED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment