Promise
trong JavaScript chắc hẳn không còn xa lạ với bất kỳ ai. Một Promise
đại diện cho một "lời hứa" rằng khi gọi hàm, nó chắc chắn sẽ trả về giá trị trong tương lai. Cho dù là xử lý được (resolve) hay không (reject), thì chúng ta đều đoán được khả năng xảy ra 1 trong 2. Và chắc chắn một điều rằng cả 2 không thể xảy ra cùng một lúc.
database.query('SELECT * ...');
.then(...);
.catch(...);
Nếu nghiên cứu kỹ hơn về Promise
, bạn sẽ thấy đây là một kiểu kiến trúc rất hay và đặc biệt. Bởi vì nó không giống với các kiểu dữ liệu nguyên thuỷ nào cả, và có một vài tính chất đặc biệt như một cấu trúc dữ liệu, thừa hưởng một số phương thức mà chỉ nó mới có như then
và catch
. Hơn nữa, nếu thấy một Promise, bạn gần như chắc chắn nên xử lý nó như thế nào.
Ngoài Promise
ra, trong nhiều ngôn ngữ lập trình khác nói chung hay JavaScript nói riêng, thì người ta luôn cố gắng tạo ra những cấu trúc dữ liệu an toàn hơn cho lập trình. Trong đó có thể kể đến kiểu Maybe
. Vậy thì kiểu dữ liệu "Có lẽ" này có gì đặc biệt, nó giúp ích gì trong lập trình? Câu trả lời có trong bài viết dưới đây.
Maybe
Maybe
là một cấu trúc dữ liệu đại diện cho việc có hoặc không có giá trị. Ví dụ dưới đây là một triển khai đơn giản nhất cho Maybe:
class Maybe {
constructor(value) {
this.value = value;
}
isNothing() {
return this.value === null || this.value === undefined;
}
getOrElse(defaultValue) {
return this.isNothing() ? defaultValue : this.value;
}
}
Khi đó tạo ra một cấu trúc bằng cách:
const name = new Maybe('2coffee');
console.log(name); // Maybe {value: '2coffee'}
// hay
const unname = new Maybe(); // Maybe {value: undefined}
Hãy lần lượt đi qua các phương thức trong Maybe
.
Đầu tiên chúng ta thấy có một isNothing
. Phương thức này kiểm tra xem liệu một Maybe
là có giá trị hay không có giá trị, nó thường được dùng để xem có giá trị hay không, từ đó xử lý cho phù hợp.
getOrElse
trả về giá trị thực của Maybe
hoặc một giá trị mặc định được truyền vào nếu Maybe đó đang không có giá trị nào.
Cách dùng cũng đơn giản. Ví dụ, muốn lấy ra giá trị của unname
, nếu không có thì trả về 2coffee
.
const unname = new Maybe();
const realName = unname.getOrElse('2coffee'); // 2coffee
name
là một thực thể của Maybe
. Chúng ta biết name
chứa một chuỗi 2coffee
nhưng không thể áp dụng một số logic thông thường như đối với chuỗi. Ví dụ:
const name = new Maybe('2coffee');
const fullName = name + `.dev`; // [object Object].dev
Đó là vì name
giờ đây là một triển khai của Maybe, nó không phải là chuỗi nên không thể thực thi logic như một chuỗi. Thay vào đó chúng ta nên qua một bước lấy giá trị trong Maybe ra trước.
const name = new Maybe('2coffee');
const fullName = name.getOrElse() + '.dev'; // 2coffee.dev
Cách dùng này không an toàn, bởi vì giả sử name
không chứa giá trị thì sẽ nhận được kết quả là undefined.dev
. Nên cần phải xử lý thêm ngoại lệ cho trường hợp name
không có giá trị. Hoặc.
Các kiểu dữ liệu Maybe thường được triển khai thêm phương thức map
. Nó cho phép áp dụng một hàm vào giá trị bên trong của Maybe mà không cần phải lấy ra giá trị thực.
class Maybe {
...
static of(value) {
return new Maybe(value);
}
map(fn) {
if (this.isNothing()) {
return this;
}
return Maybe.of(fn(this.value));
}
}
map
nhận vào một hàm để áp dụng hàm đó vào giá trị bên trong Maybe. static of
để gói lại giá trị vào Maybe và trả về.
const name = new Maybe('2coffee');
const makeFullName = (name) => name + '.dev'
const fullName = name.map(makeFullName); // Maybe {value: '2coffee.dev'}
Hơi khó hiểu nhỉ? thế tại sao không khai báo luôn name = '2coffee'
đi mà còn phải đi qua Maybe
? Bời vì lý do đằng sau cho việc sử dụng Maybe
mang lại một số lợi ích nhất định trong các trường hợp nhất định.
Việc áp dụng Maybe trong lập trình giúp chúng ta giảm thiểu được lỗi null
và undefined
. Bởi vì khi gặp giá trị Maybe
, chúng ta bắt buộc phải xử lý dữ liệu theo kiểu Maybe
.
const user = null;
const name = user.name; // TypeError: Cannot read properties of null
// so với
const user = Maybe.of(null);
const name = user.map((u) => u.name); // Maybe {value: null}
Một điều mình rất thích và luôn muốn viết chương trình theo kiểu chuỗi xử lý (chaining). Maybe hoàn toàn có thể đáp ứng mà không lo "break" khi gặp giá trị null
hoặc undefined
.
const data = {
user: {
address: {
city: "Hanoi"
}
}
};
const city = Maybe.of(data)
.map((d) => d.user)
.map((user) => user.profile)
.map((address) => address.city);
Ở ví dụ trên, ở map
thứ 2, rõ ràng user.profile
trả về undefined
nhưng map
thứ 3 vẫn có thể thực thi mà không gây ra lỗi "Cannot read properties of undefined", vì map
đã xử lý ngoại lệ này.
Tương tự, Maybe
buộc chúng ta phải xử lý trường hợp không có giá trị. Hãy tưởng tượng Maybe
giống như Promise
. Một khi đã gọi thì luôn luôn trả về 1 trong hai giá trị là có giá trị và không có giá trị, trong mỗi trường hợp thì phải xử lý sao cho đúng cách để chương trình không gây lỗi trong quá trình chạy.
function findUserById(id) {
const user = database.find((u) => u.id === id);
return Maybe.of(user); // Trả về Maybe thay vì null
}
const userName = findUserById(1)
.map((user) => user.name)
.getOrElse("User not found");
Ngoài ra, nếu áp dụng tốt Maybe
thì chương trình còn trở nên gọn gàng, sạch sẽ hơn nữa. Đôi khi sẽ không còn những câu lệnh điều kiện if-else
đầy rối rắm.
Maybe
là một cấu trúc dữ liệu đại diện cho việc có hoặc không có giá trị. Áp dụng Maybe trong lập trình giúp chúng ta giảm thiểu được lỗi null
và undefined
, xử lý được chaining, phải xử lý tất cả trường hợp có hoặc không có dữ liệu trả về... Bên cạnh Maybe
, còn có một cấu trúc dữ liệu khác nữa có tên là Either
, bổ sung thêm một số tính chất mà Maybe
không có. Chúng ta sẽ cùng nhau tìm hiểu trong bài viết tiếp theo nhé!
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!
Đăng ký nhận thông báo bài viết mới
Bình luận (0)