Khi mới bắt đầu học JavaScript và React, mình đã từng bối rối với khái niệm Promise. Nó giống như một thứ gì đó mơ hồ và khó nắm bắt. Nhưng sau một thời gian tìm hiểu và thực hành, mình nhận ra rằng Promise thực sự là một công cụ mạnh mẽ giúp chúng ta xử lý các tác vụ bất đồng bộ một cách hiệu quả.
Trong bài viết này, mình muốn chia sẻ với các bạn:
- Promise là gì?
- Cách sử dụng Promise trong JavaScript.
- Hướng dẫn áp dụng Promise trong dự án React.
Hy vọng sau khi đọc xong, các bạn sẽ có cái nhìn rõ ràng hơn về Promise và tự tin áp dụng nó vào dự án của mình.
1. Bất đồng bộ trong JavaScript là gì?
Trước khi đi vào chi tiết về Promise, bạn cần hiểu về bất đồng bộ trong JavaScript. JavaScript là ngôn ngữ đơn luồng (single-threaded), nghĩa là nó chỉ thực thi một tác vụ tại một thời điểm. Tuy nhiên, trong thực tế, chúng ta thường phải xử lý nhiều tác vụ cùng lúc như call API, đọc/ghi file, hoặc xử lý sự kiện người dùng.
Nếu như bạn thực hiện các tác vụ trên một cách đồng bộ, ứng dụng sẽ bị "đóng băng" cho đến khi các tác vụ hoàn thành. Điều đó sẽ tạo ra trải nghiệm người dùng không hề tốt. Để giải quyết vấn đề này, JavaScript sử dụng cơ chế bất đồng bộ để cho phép các tác vụ diễn ra song song mà không làm gián đoạn luồng chính.
Nếu bạn mong muốn hiểu sâu hơn về cách mà JavaScript xử lý các tác vụ bất đồng bộ thì cá nhân mình khuyên bạn nên tìm hiểu về Event Loop.
2. Promise là gì?
Promise là một đối tượng trong JavaScript đại diện cho một tác vụ bất đồng bộ có thể hoàn thành trong tương lai. Nó giống như một lời hứa: "Tôi hứa sẽ trả kết quả cho bạn khi tác vụ hoàn thành".
Một Promise có thể ở một trong ba trạng thái sau:
- Pending (đang chờ): trạng thái ban đầu, chưa hoàn thành hoặc bị từ chối.
- Fulfilled (hoàn thành): tác vụ đã hoàn thành thành công.
- Rejected (bị từ chối): tác vụ đã thất bại.
3. Tại sao cần sử dụng Promise?
Trước khi Promise xuất hiện, bạn sẽ thường sử dụng callback để xử lý bất đồng bộ đúng không. Tuy nhiên, việc lồng ghép nhiều callback dẫn đến "Callback Hell", khiến code khó đọc và khó bảo trì.
Promise giúp giải quyết vấn đề này bằng cách cung cấp cách viết code rõ ràng và tuyến tính, dễ dàng quản lý các tác vụ bất đồng bộ và xử lý lỗi hơn.
4. Hướng dẫn sử dụng Promise trong JavaScript
4.1 Tạo một Promise
Để tạo một Promise, bạn sẽ sử dụng new Promise
, truyền vào một hàm executor với hai tham số là resolve và reject.
const myPromise = new Promise((resolve, reject) => {
resolve(ket_qua);
reject(loi);
});
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Success!');
} else {
reject('Failed!');
}
}, 1000);
});
Trong ví dụ này, sau 1 giây, Promise sẽ được hoàn thành với kết quả "Success!" hoặc bị từ chối với "Failed!".
4.2 Xử lý kết quả với .then() và .catch()
Để nhận kết quả từ một Promise, bạn có thể sử dụng phương thức .then()
và .catch()
.
- .then(onFulfilled): được gọi khi Promise được hoàn thành.
- .catch(onRejected): được gọi khi Promise bị từ chối.
promise
.then((result) => {
console.log(result); // "Success!"
})
.catch((error) => {
console.error(error); // "Failed!"
});
4.3 Chaining Promises
Bạn có thể nối chuỗi các Promise bằng cách trả về một Promise trong .then().
firstPromise()
.then((result1) => {
return secondPromise(result1);
})
.then((result2) => {
console.log(result2);
})
.catch((error) => {
console.error(error);
});
4.4 Sử dụng Promise.all
Promise.all cho phép chúng ta chạy nhiều Promise song song và nhận kết quả khi tất cả đều hoàn thành.
const promise1 = fetchData1();
const promise2 = fetchData2();
Promise.all([promise1, promise2])
.then(([result1, result2]) => {
console.log(result1, result2);
})
.catch((error) => {
console.error(error);
});
Lưu ý quan trọng mà bạn nên nhớ là: luôn xử lý lỗi bằng cách sử dụng .catch()
. Nếu bạn bỏ qua, lỗi có thể không được phát hiện và gây ra những vấn đề khó debug.
5. Hướng dẫn sử dụng Promise trong React
Trong React, thường cần thực hiện các tác vụ bất đồng bộ như call API để lấy dữ liệu, gửi dữ liệu lên server, hoặc thực hiện các tính toán phức tạp. Sử dụng Promise giúp chúng ta quản lý những tác vụ này một cách hiệu quả.
5.1 Sử dụng fetch API với Promise
fetch là một API tích hợp trong JavaScript để get dữ liệu, và nó trả về một Promise.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error('Lỗi:', error);
});
Trong ví dụ này, chúng ta gọi đến API tại: https://jsonplaceholder.typicode.com/users
để lấy danh sách người dùng.
5.2 Sử dụng Promise, useEffect trong component
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => {
setUsers(data);
})
.catch((error) => {
console.error('Lỗi:', error);
});
}, []);
return (
<div>
{users ? (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
) : (
<div>Loading...</div>
)}
</div>
);
}
export default UserList;
useEffect
với dependencies rỗng [] sẽ chỉ chạy một lần khi component được mount.- Sử dụng fetch để gọi API và cập nhật state users khi nhận được kết quả.
6. So sánh Promise và Async/Await
Mặc dù bài viết này tập trung vào Promise, nhưng mình muốn chia sẻ một chút về Async/Await để các bạn có cái nhìn tổng quan hơn.
Với Promise:
- Sử dụng .then() và .catch() để xử lý kết quả và lỗi.
- Mã có thể trở nên phức tạp khi nối chuỗi nhiều Promise.
- Dễ gặp phải vấn đề về "Callback Hell" nếu không quản lý được tốt.
function fetchData() {
return fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => {
console.log('Result:', data);
})
.catch((error) => {
console.error('Err:', error);
});
}
fetchData();
Với Async/await:
- Giúp code trông giống như mã đồng bộ, dễ đọc và hiểu hơn.
- Sử dụng try...catch để xử lý lỗi.
- Tránh được việc lồng ghép quá nhiều .then().
async function fetchData() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
console.log('Result:', data);
} catch (error) {
console.error('Err:', error);
}
}
fetchData();
Như bạn có thể thấy, với Async/Await, code bạn trông gọn gàng và dễ đọc hơn. Tuy nhiên, việc sử dụng Async/Await cũng yêu cầu bạn hiểu rõ về Promise, vì vốn dĩ nó chỉ là một cú pháp mới dựa trên Promise.
- Sử dụng Promise khi bạn cần quản lý nhiều tác vụ bất đồng bộ cùng lúc và cần sử dụng các phương thức như: Promise.all, Promise.race. Ví dụ như thế này
for (const item of items) {
await processItem(item);
}
bạn nên thay đổi sử dụng thế này:
await Promise.all(items.map((item) => processItem(item)));
- Sử dụng Async/Await khi bạn muốn code trông giống như đồng bộ, dễ đọc và cần thực hiện các tác vụ bất đồng bộ tuần tự.
7. Kết luận
Promise là một phần quan trọng trong JavaScript, đặc biệt khi làm việc với React. Hiểu và sử dụng thành thạo Promise sẽ giúp bạn viết code bất đồng bộ cách hiệu quả, tránh các vấn đề về hiệu suất và cải thiện trải nghiệm người dùng.
Hy vọng qua bài viết này, bạn đã nắm được những kiến thức cơ bản về Promise. Nếu bạn mới bắt đầu, đừng ngại thực hành nhiều. Hãy tự tạo các ví dụ nhỏ, và bạn sẽ dần hiểu sâu hơn về cách mà Promise hoạt động.
Các bài viết liên quan:
Bài viết liên quan
Render Props pattern là gì? Hướng dẫn sử dụng Render Props
Dec 03, 2024 • 8 min read
HOCs Pattern là gì? Hướng dẫn triển khai Hocs Pattern trong dự án React
Dec 02, 2024 • 7 min read
Hooks Pattern là gì? Hướng dẫn áp dụng Hooks Pattern trong dự án React
Nov 28, 2024 • 11 min read
Async/await là gì? Hướng dẫn sử dụng Async/await trong dự án React
Nov 26, 2024 • 8 min read
Một số Phương pháp cải thiện Performance trong dự án React
Nov 25, 2024 • 16 min read
Hướng dẫn tích hợp Redux và React Query trong dự án React Vite
Nov 22, 2024 • 8 min read