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

Những mẩu tin ngắn hàng ngày dành cho bạn
  • Hẳn là nhiều người ở đây đã nghe đến kiểu tấn công bảo mật Clickjacking rồi nhỉ. Kẻ tấn công thường nhúng một website (thường là mục tiêu) vào trong một iframe trên website của chúng, sau đó làm mờ hoặc ẩn nó đi rồi đặt vào vị trí các nút bấm trên web, ví dụ "Bấm vào để nhận quà". Đâu ai ngờ rằng phía trên nút bấm đó là một nút bấm khác trong iframe. Khá nguy hiểm!

    Nhưng trình duyệt đã có cách ngăn chặn kiểu tấn công này bằng các quy tắc như tiêu đề X-Frame-Options, frame-ancestors của CSP và SameSite: Lax/Strict của Cookies...

    Mới đây, đã xuất hiện thêm kiểu tấn công mới - "DoubleClickjacking" 😨. Đại ý là "hắn" lợi dụng hành động double click để lừa người dùng bấm vào một nút mà hắn muốn. Chi tiết hơn trong bài viết này: DoubleClickjacking: A New Era of UI Redressing.

    » Xem thêm
  • Mọi người đã nghe nói đến Jujutsu - jj - một dạng quản lý phiên bản cho mã nguồn (version control system) chưa? Có vẻ như nó đang nhận được nhiều sự quan tâm.

    Chờ xíu! Chẳng phải git đã quá tốt rồi sao? Thế thì chế ra thằng jj để làm gì nữa? Cũng hơi khó trả lời nhỉ? Mỗi công cụ sinh ra chắc chắn phải cải thiện hoặc khắc phục được nhược điểm của cái trước. Cho nên jj ắt hẳn phải làm được điều gì đó mà git chưa làm được nên mới nổi lên như vậy.

    Thật ra mình đã nghe nói đến jj từ vài tháng trước rồi, nhưng vào đọc thì toàn kiến thức cao siêu. Hoặc là đang mang nặng cái lối suy nghĩ của git vào trong đầu rồi nên chưa lĩnh hội ra được điều gì cả.

    Mình hay có kiểu cái gì đọc lần 1 mà không hiểu thì đọc tiếp lần 2, lần 2 không hiểu thì đọc tiếp lần 3... đến lần thứ n mà vẫn không hiểu thì bỏ. Cơ mà không phải là từ bỏ mà một thời gian sau đó quay lại đọc tiếp. Đến một lúc nào đó khả năng mình sẽ hiểu ra một ít vấn đề, thế mới tài 😆.

    Thì cái jj này có vẻ như nó đang mở ra được tính linh hoạt trong việc "cam kết" mã. Tưởng tượng bạn đang làm việc trên một dự án, đang ở nhánh này, muốn sang nhánh khác để sửa, nhưng mà lại đang viết dở ở nhánh này, thế là phải stash, rồi checkout, rồi commit, rồi merge hoặc rebase lại vào nhánh cũ... nhìn chung quá trình làm việc với git nghiêm ngặt đến mức cứng nhắc, cần nhiều thao tác để giải quyết một vấn đề, chưa kể cái cây commit (commit-tree) nữa thì ôi thôi, khỏi xem cho đỡ nhức mắt. Thế nên ông jj này đang làm cách nào đó để bạn khỏi cần phải quan tâm đến các nhánh luôn, sửa trực tiếp vào commit. Nghe ảo nhỉ 😂.

    Đấy mới lĩnh hội được đến đấy, hy vọng sau n lần đọc lại nữa mình sẽ viết được một bài chi tiết hơn về công cụ này.

    » Xem thêm
  • Gòi gòi tới công chiện gòi 🤤🤤🤤

    » 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

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào 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...
Bấm hoặc cuộn mạnh để sang bài mới