Triển khai mã hiệu quả hơn với compose & pipe function trong Javascript

Triển khai mã hiệu quả hơn với compose & pipe function trong Javascript

Tin ngắn hàng ngày dành cho bạn
  • Kết thúc kỳ nghỉ, để thấy nó dữ dội đến đâu thì tổng số bước đã đi trong 4 ngày là 45k các bạn ạ 😆

    » Xem thêm
  • Đây là bài viết thứ 366, có nghĩa là mình đã duy trì được việc mỗi ngày một bài trong chuyên mục Threads 1 năm rồi đấy mọi người. Tuy rằng không phải ngày nào cũng viết vì nhiều hôm mình bận, quên thì hôm sau là lên bài bù, mục đích là để cam kết với độc giả, ấy vậy mà quay đi quay lại đã một năm trôi qua rồi. Nhanh thật 😃

    À mai, kia, ngày kìa nữa mình đi du lịch nên chắc không lên bài cho mọi người được. Về rồi mình lên sau nhé 😅. Cảm ơn!

    » Xem thêm
  • Hơn 1 tuần nay mình không đăng bài, không phải không có gì để viết mà đang tìm cách để phân phối nội dung có giá trị hơn trong thời đại AI đang bùng nổ mạnh mẽ như thế này.

    Như từ hồi đầu năm đã chia sẻ, số lượng người truy cập vào trang blog của mình đang dần ít đi. Khi xem thống kê, lượng người dùng trong 6 tháng đầu năm 2025 đã giảm 30% so với cùng kì năm ngoái, 15% so với 6 tháng cuối năm 2024. Như vậy một sự thật là người dùng đang rời bỏ dần đi. Nguyên nhân do đâu?

    Mình nghĩ lý do lớn nhất là thói quen của người dùng đã thay đổi. Họ tìm thấy blog chủ yếu qua các công cụ tìm kiếm, trong đó lớn nhất là Google. Gần 1/2 số lượng người dùng quay trở lại blog mà không cần thông qua bước tìm kiếm. Đó là một tín hiệu đáng mừng nhưng vẫn không đủ để tăng lượng người dùng mới. Chưa kể giờ đây, Google đã ra mắt tính năng AI Search Labs - tức là AI hiển thị luôn nội dung tổng hợp khi người dùng tìm kiếm, điều đó càng khiến cho khả năng người dùng truy cập vào trang web thấp hơn. Một điều thú vị là khi Search Labs được giới thiệu, thì các bài viết bằng tiếng Anh đã soán ngôi trong bảng xếp hạng truy cập nhiều nhất.

    Một bài viết của mình thường rất dài, có khi lên đến cả 2000 chữ. Mà để viết ra được một bài như thế tốn nhiều thời gian. Nhiều bài viết ra chẳng có ai đọc là điều bình thường. Mình biết và chấp nhận vì không phải ai cũng gặp phải vấn đề đang nói đến. Viết đối với mình như một cách để rèn luyện sự kiên nhẫn và cả tư duy. Viết ra mà giúp được cả ai đó là một điều tuyệt vời.

    Vậy nên mình đang nghĩ sẽ tập trung vào nội dung ngắn và trung bình để viết được nhiều hơn. Nội dung dài chỉ khi muốn viết chi tiết hoặc đi sâu về một chủ đề nào đó. Nên là đang tìm cách thiết kế lại trang blog. Mọi người cùng chờ nha 😄

    » Xem thêm

Giới thiệu về composition function

Lưu ý: Trước khi tiếp tục đọc bài viết này tôi khuyên bạn nên tìm hiểu trước về Curry function là gì? Một món "cà ri" ngon và làm sao để thưởng thức nó? hoặc nếu đã biết về curry function bạn có thể tiếp tục đọc bài viết.

Composition là một cơ chế kết hợp nhiều hàm đơn giản để xây dựng một hàm phức tạp hơn. Kết quả của mỗi hàm sẽ được chuyển cho hàm tiếp theo.

Nó giống như trong toán học, chúng ta có một hàm số f(g(x)), tức là kết quả của g(x) được chuyển cho hàm f. Thì composition là như vậy.

Một ví dụ đơn giản: Viết hàm thực hiện phép tính 1 + 2 * 3.

Đối với phép tính này chúng ta phải thực hiện phép nhân trước sau đó đến phép cộng. Đoạn mã khi được triển khai bằng các hàm trong Javascript sẽ trông như thế này:

const add = (a, b) => a + b;
const mult = (a, b) => a * b;
add(1, mult(2, 3));

Oh! hàm chạy rất tốt tuy nhiên có hơi rối một chút nhỉ. Giả sử bây giờ tôi muốn chia tất cả cho 4 thì sao? Một đoạn mã gì đó sẽ trông như thế này:

div(add(1, mult(2, 3)), 4);

Chà rối hơn rồi đấy!

Bây giờ chúng ta sẽ đi đến một ví dụ khác. Giả sử tôi có một danh sách users bao gồm tên và tuổi, hãy lấy ra tên của những người trên 18 tuổi. Đoạn mã đó sẽ giống như:

const users = [
  { name: "A", age: 14 },  
  { name: "B", age: 18 }, 
  { name: "C", age: 22 },  
];

const filter = (cb, arr) => arr.filter(cb);
const map = (cb, arr) => arr.map(cb);

map(u => u.name, filter(u => u.age > 18, users)); // ["C"]

Tư tưởng là tôi sẽ tạo ra 2 hàm filter & map, filter để lọc còn map là để duyệt qua các phần tử. Đoạn mã trên hoạt động tốt tuy nhiên cũng như ví dụ đầu tiên, nó có hơn rườm rà một chút.

Vậy thì có cách nào giải quyết được ổn thoả hai vấn đề trên? Hoặc chí ít là giúp cho mã rõ ràng hơn khi điều kiện bài toán tăng thêm.

Triển khai

Hàm compose

Mục tiêu của tôi là sẽ tạo ra một hàm nhận vào nhiều tham số, các tham số này là những hàm nhỏ hơn để thực hiện một khối lượng công việc nhất định (Higher Order Function).

Nó sẽ trông giống như là hàm compose này:

compose(function1, function2…, functionN): Function

compose nhận vào các hàm và trả ra một hàm. Tư tưởng của compose là khi nó được gọi, nó sẽ thực hiện các hàm trong tham số từ phải sang trái, kết quả của hàm trước sẽ được chuyển thành đối số của hàm sau.

Đây là một cách đơn giản để implement hàm compose bằng ES6:

const compose = (...functions) => args => functions.reduceRight((arg, fn) => fn(arg), args);

Sẽ thật là hạnh phúc với tôi nếu bạn hiểu được những gì bên trong compose thực sự làm, còn nếu không hiểu thì hãy comment ở phía dưới bài viết nhé.

Bây giờ quay trở lại với ví dụ ban đầu, ta hãy sửa lại mã một chút:

const add = a => b => a + b;
const mult = a => b => a * b;

const operator = compose(add(1), mult(2));
const result = operator(3);

// Hoặc ngắn gọn hơn chúng ta cũng có thể viết

const result = compose(add(1), mult(2))(3);

Tôi đã biến addmult thành hàm curry, bời vì sao? Bởi vì khi chuyển nó thành curry tôi có thể dùng nó như là một tham số là hàm vào trong compose.

Được rồi bây giờ muốn tất cả chia cho 4 thì sao?

const div = a => b => b / a;
const result = compose(div(4), add(1), mult(2))(3);

Thật dễ đọc phải không. Từ trái sang phải lần lượt thực hiện nhân với 2, sau đó cộng thêm 1 và cuối cùng chia cho 4. Cứ giống như một dòng chảy vậy.

Tương tự như vậy với ví dụ 2 hãy sửa lại mã của nó một chút:

const users = [
  { name: "A", age: 14 },  
  { name: "B", age: 18 }, 
  { name: "C", age: 22 },  
];

const filter = cb => arr => arr.filter(cb);
const map = cb => arr => arr.map(cb);

compose(
  map(u => u.name),  
  filter(u => u.age > 18),  
)(users); // ["C"]

Chúng ta có thể viết thêm một cơ số hàm nối tiếp ở trong compose mà vẫn giữ được dòng chảy dữ liệu hoạt động, và hơn hết là giữ cho đoạn mã tương đối dễ đọc.

Hàm pipe

Tương tự như compose, pipe cũng mang tư tưởng giống như compose chỉ duy nhất khác một điều là thứ tự thực hiện các hàm trong tham số là từ trái sang phải.
Hàm pipe sẽ được implement như thế này:

const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args);

Áp dụng pipe vào ví dụ 1:

const add = a => b => a + b;
const mult = a => b => a * b;

const result = pipe(mult(2), add(1))(3);

Như các bạn thấy các tham số được truyền vào ngược lại so với compose, nó sẽ thực hiện các hàm từ phải sang trái: nhân 3 với 2 rồi sau đó cộng 1.

Bạn có thể sử dụng composepipe tuỳ theo sở thích hoặc thói quen vì hai hàm đều mang lại kết quả tương tự nhau.

Tổng kết

Hai hàm composepipe tuy nhỏ nhưng nó mang lại lợi ích rất lớn trong việc áp dụng vào các bài toán xử lý dữ liệu liên tục. Nó giúp mã rõ ràng & dễ đọc hơn.

Hầu như các thư viện Javascript hỗ trợ việc xử lý dữ liệu đều có sẵn các hàm tương tự như compose hoặc pipe như _.compose, _.pipe trong lodash hay compose, pipe trong ramdajs.

Hy vọng sau bài viết này các bạn sẽ có thêm một phương pháp để xử lý dữ liệu trong các dự án sắp tới. Ngay bây giờ nếu gặp các vấn đề tương tự như trên hãy thử áp dụng ngay bạn nhé!

Cao cấp
Hello

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...