Ôn tập callback, promise, async/await
Châm ngôn của mình là học để kiếm tiền.
Vì thế mình build các khóa học của mình để giúp anh em tiến bộ nhanh hơn x10 lần , để kiếm được nhiều tiền hơn
- 🏆 React.js Super: Trở thành React.js Developer trong 7 ngày với mức thu nhập 20 triệu/tháng
- 🏆 Node.js Super: Giúp bạn học cách phân tích, thiết kế, deploy 1 API Backend bằng Node.js
- 🏆 Next.js Super: Mình sẽ chia sẻ từ A-Z kiến thức về Next.js, thứ giúp mình kiếm hơn 1 tỉ/năm
Trong quá trình dạy cho hơn 800 học viên, mình nhận ra rằng nhiều bạn học JavaScript cơ bản chưa vững đã vội nhảy sang học React, Vue, NodeJS, ... và gặp rất nhiều khó khăn khi xử lý bất đồng bộ.
Mục tiêu bài viết này là mình sẽ giúp các bạn ôn tập hầu như mọi trường hợp các bạn hay gặp phải khi xử lý callback, promise, async/await trong JavaScript. Đủ để các bạn chinh chiến mọi thứ hoặc trả lời phỏng vấn suôn mượt.
Đây là ôn tập chứ không phải học từ đầu, nên mình sẽ không giải thích định nghĩa này nọ chi tiết đâu.
Các bạn phải biết cơ bản về callback, promise, async await là gì trước khi đọc nhé.
🥇Hiểu lầm lớn nhất khi mới học JavaScript
Chúng ta đều biết JavaScript là ngôn ngữ lập trình có khả năng xử lý bất đồng bộ. Vậy nên khi mới học JavaScript, các bạn newbie thường nghĩ rằng code trong JavaScript chạy "lộn xộn", đoạn code A chạy chưa xong là đến đoạn code B
Ví dụ đoạn code dưới đây, một số bạn nghĩ console.log('Done')
sẽ chạy trước khi vòng for
hoàn thành, vì bất đồng bộ mà 😂.
// Vòng for 100 lần
for (let i = 0; i < 100; i++) {}
console.log('Done')
Nhưng điều này là sai nha, bất đồng bộ trong JavaScript chỉ xảy ra khi chúng ta gọi đến một hàm bất đồng bộ, ví dụ như setTimeout
, fetch
, axios
, fs.readFile
, fs.writeFile
, ...
Còn những đoạn code bình thường như for
, if
, while
, switch
, ... thì vẫn chạy tuần tự như bình thường.
Chúng ta cần phải biết cái nào là bất đồng bộ, cái nào là đồng bộ, để biết được đoạn code nào chạy trước, đoạn code nào chạy sau. Thường những đoạn code bất đồng bộ sẽ có callback, promise, async/await.
OK, đơn giản vậy thôi, nhưng nhiều bạn không rõ đấy 😁
🥇Callback
Callback là một hàm được truyền vào một hàm khác như một tham số.
Cái function callback
được truyền vào setTimeout
là một callback. Nó sẽ được gọi sau 1 giây khi setTimeout
được gọi.
function callback() {
console.log('Hello World')
}
setTimeout(callback, 1000)
Callback thường được dùng trong xử lý bất đồng bộ, nhưng lưu ý rằng callback vẫn có thể là đồng bộ, không nhất thiết cứ callback là auto nó bất đồng bộ.
Ví dụ cái function callback
được truyền vào syncFunction
được gọi là một callback. Nó sẽ được gọi ngay lập tức khi syncFunction
được gọi. Như bạn thấy đấy, chả có bất đồng bộ gì ở đây cả.
function callback() {
console.log('Hello World')
}
function syncFunction(cb) {
cb()
}
syncFunction(callback)
🥇Promise
Dân tình có câu "Javascript Promises are Eager and Not Lazy"
Trong ngữ cảnh của JavaScript và Promises, "eager" có nghĩa là Promise sẽ được thực thi ngay lập tức khi nó được tạo ra, ngay cả trước khi bạn gọi phương thức then()
để xử lý kết quả.
Khi bạn tạo ra một Promise, một công việc bất đồng bộ sẽ được khởi tạo. Promise sẽ bắt đầu thực hiện công việc đó ngay lập tức, ngay cả khi bạn chưa sử dụng phương thức then()
để xử lý kết quả. Điều này có nghĩa là Promise sẽ bắt đầu thực thi công việc bất đồng bộ và không chờ đợi cho việc gọi then()
.
Ví dụ, trong đoạn mã sau:
const promise = new Promise((resolve, reject) => {
console.log('Executing promise')
resolve('Success')
})
promise.then((result) => {
console.log('Promise resolved:', result)
})
console.log('Promise created')
Đầu tiên, khi bạn tạo một Promise, đoạn mã trong hàm khởi tạo sẽ được thực thi và thông báo "Executing promise" sẽ được log ra. Tiếp theo, "Promise created" sẽ được log ra. Cuối cùng, khi bạn gọi phương thức then()
trên Promise, hàm callback trong then()
mới được gọi và thông báo "Promise resolved: Success" sẽ được log ra.
Nếu bạn muốn khi then()
thì mới được gọi thì bạn phải chuyển nó thành một function return promise. Như thế này mới gọi là lazy nhá.
const promiseFunc = () =>
new Promise((resolve, reject) => {
console.log('Executing promise')
resolve('Success')
})
promiseFunc().then((result) => {
console.log('Promise resolved:', result)
})
console.log('Promise created')
Với cách này thì khi chúng ta gọi promiseFunc()
thì "Executing promise" mới được log ra. Kết quả đoạn code trên là
Executing promise
Promise created
Promise resolved: Success
Có thể bạn thấy nó không khác gì cái code trên đó, nhưng nếu comment cái dòng lệnh promiseFunc().then((result) => {...
đi thì bạn sẽ thấy nó không log ra "Executing promise" nữa. Còn cái code trên thì nó vẫn log ra "Executing promise" ngay cả khi chúng ta không gọi promise.then((result) => {...
.
Oke, tiếp tục dưới đây là những đoạn code ngắn gọn, có thể coi là cheatsheet cũng được, nó sẽ giúp các bạn ôn luyện nhanh promise
Chuyển một callback thành một promise
const getProduct = new Promise((resolve) => {
// setTimeout sẽ được gọi ngay lập tức
setTimeout(() => {
// còn callback trong này sau 1s sẽ chạy
console.log('setTimeout')
resolve([])
}, 1000)
})
Promise là 1 biến chứ không phải là function gì cả nhé, biến đây là object. Còn thứ chúng ta hay thấy khi gọi api kiểu như getApi().then()
thì khi gọi getApi()
, nó return về 1 promise chứ không phải getApi
là 1 promise
// Tạo nhanh một promise mà sẽ resolve
const presolve = Promise.resolve(100)
// Tạo nhanh một promise mà sẽ reject
const preject = Promise.reject(new Error('loi'))
Một số function mà return về promise tương đương nhau
// Một async function sẽ return về một promise
// Cho dù giá trị return bên trong function không phải là promise
const handle = async () => {
return 'hello'
}
// Trong trường hợp giá trị return là một promise
// Thì mọi thứ vẫn không thay đổi, vẫn như trên
// Không có chuyện lồng nhau như `handle().then(promise => promise.then(res => {console.log(res)}))`
const handle2 = async () => {
return Promise.resolve('hello')
}
// Đây cũng là một function return promise tương tự 2 trường hợp trên
// Chỉ khác là nó không khai báo async thôi
const handle3 = () => {
return Promise.resolve('hello')
}
Xử lý promise chain
Trong .then()
chúng ta return cái gì thì nó sẽ là data cho cái then tiếp theo. Cho dù bạn có return về 1 promise trong then thì nó cũng giống return thường
handle3()
.then((res) => {
// 2 cái return này tương đương nhau
// return Promise.resolve(res + ' hi')
return res + ' hi'
})
.then((res) => {
console.log(res)
})
Nếu muốn ở chain tiếp theo không nhảy vào then mà nhảy vào .catch thì cần throw()
hoặc return Promise.reject()
trong .then()
trước đó
handle3()
.then((res) => {
return Promise.reject(new Error('error'))
// Dùng new Error để khi nó log ra nó kèm dấu vết bị lỗi
// throw new Error('error')
})
.then((res) => {
console.log(res)
})
.catch((e) => {
console.log(e)
})
Xử lý phụ thuộc khi dùng promise, ví dụ function handle4
cần kết quả của function handle2
trả về
const handle4 = (value) => {
return Promise.resolve('handle4 ' + value)
}
// ❌ callback hell, không nên dùng
// handle2().then((res) => {
// handle4(res).then((v) => {
// console.log(v)
// })
// })
// ✅ hạn chế được callback hell
handle2()
.then((res) => {
return handle4(res)
})
.then((v) => {
console.log(v)
})
1 promise mà nó reject thì sẽ làm crash ứng dụng. Muốn không bị crash thì phải luôn luôn .catch
nó.
catch
mà không không làm gì trong đó (ví dụ không console.log
) thì khi nó lỗi không biết lỗi chỗ nào đấy (lỗi vô hình)
Một khi đã .catch
thì cái chain phía sau luôn luôn là promise.resolve
, trừ khi bạn throw
hoặc return Promise.reject
trong .catch
handle2()
.then((res) => {
throw new Error('error')
})
.catch((e) => {
console.log('Chắc chắn sẽ nhảy vào đây')
})
.then((v) => {
console.log('Nhảy vào đây vì trước đó đã catch, và giá trị v là undefined vì trong catch không return gì cả')
})
🥇Async/Await
Async await giúp chúng ta loại bỏ được callback trong promise. Nhưng vẫn không thoát khỏi promise nhé, chúng nó kế thừa lẫn nhau thôi.
Lưu ý quan trọng:
- Chỉ nên dùng async await cho function mà bên trong có xử lý promise
- Chỉ await được promise, không await được callback hay mấy cái function thường
- Async function là function return về promise
const p = Promise.resolve('hello')
// Muốn dùng async await cho cái promise trên thì phải tạo 1 function
// Vì async chỉ dùng cho function
// main là 1 function return về promise nhé
const main = async () => {
const data = await p
console.log(data)
}
main()
Tương tự khi dùng promise thì phải thêm .catch()
, dùng async await thì phải try catch
, không thì khi nó lỗi nó sẽ crash app.
const main = async () => {
try {
const data = await p
console.log(data)
} catch (error) {
console.log(new Error(error))
}
}
main()
Khi throw trong 1 async function thì nó sẽ ngay lập tức cho function đó return một promise.reject()
const main = async () => {
const data = await p
console.log(data)
throw new Error('Chủ động quăng lỗi')
}
main()
Nếu có nhiều async function lồng nhau, không cần try catch từng async function, mà chỉ cần try catch async function ngoài cùng là được.
Lưu ý: phải đảm bảo dùng await cho async function bên trong thì mới được nhé
const getApi1 = () => {
return Promise.reject(new Error('Loi API'))
}
const getApi2 = async () => {
await getApi1()
}
const getApi3 = async () => {
try {
await getApi2()
} catch (error) {
// xử lý lỗi tại đây
}
}
Tương tự bên promise, một khi đã try catch
thì function luôn return một promise.resolve(x)
trừ khi bạn chủ động throw
hoặc return Promise.reject(x)
🥇Promise.all
Dùng promise.all
khi ta muốn chạy song song nhiều promise, và khi tất cả các promise đó đều resolve thì mới thực hiện tiếp.
Ứng dụng khi ta có nhiều promise không phụ thuộc kết quả của nhau
Ví dụ dưới đây, promise1, promise2, promise3 không phụ thuộc kết quả của nhau, nên ta có thể dùng promise.all
để chạy song song 3 cái promise này, thay vì đợi từng cái chạy xong rồi mới chạy cái tiếp theo.
const promise1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 1'), 2000)
})
const promise2 = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 2'), 1000)
})
const promise3 = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise 3'), 1500)
})
Promise.all([promise1(), promise2(), promise3()])
.then((results) => {
console.log('Results:', results)
})
.catch((error) => {
console.log('Error:', error)
})
// Hoặc dùng async await
const main = async () => {
try {
const [result1, result2, result3] = await Promise.all([promise1(), promise2(), promise3()])
} catch (error) {
console.log('Error:', error)
}
}
Áp dụng promise.all
đúng cách sẽ giúp tối ưu hiệu suất ứng dụng lên nhiều lần.
🥇Tổng kết
Bài này nói chung là những ghi chú của mình về callback, promise, async await. Mong rằng nó giúp các bạn ôn tập lại kiến thức để sẵn sàng học React, Vue, Node.js,...dễ dàng hơn.
Kiến thức trong khóa học Next.js này đã giúp mình kiếm hơn 1 tỉ đồng/năm
Phew! Cuối cùng bạn cũng đã đọc xong. Bài viết này có hơi dài một tí vì mình muốn nó đầy đủ nhất có thể 😅
Website bạn đang đọc được viết bằng Next.js TypeScript và tối ưu từng chi tiết nhỏ như SEO, hiệu suất, nội dung để đảm bảo bạn có trải nghiệm tốt nhất.
Với lượt view trung bình là 30k/tháng (dù website rất ít bài viết). Website này đem lại doanh thu 1 năm vừa qua là hơn 1 tỉ đồng
Đó chính là sức mạnh của SEO, sức mạnh của Next.js.
Mình luôn tin rằng kiến thức là chìa khóa giúp chúng ta đi nhanh nhất.
Mình đã dành hơn 6 tháng để phát triển khóa học Next.js Super | Dự án quản lý quán ăn & gọi món bằng QR Code. Trong khóa này các bạn sẽ được học mọi thứ về framework Next.js, các kiến thức từ cơ bản cho đến nâng cao nhất, mục đích của mình là giúp bạn chinh phục mức lương 25 - 30 triêu/tháng
Nếu bạn cảm thấy bài viết này của mình hữu ích, mình nghĩ bạn sẽ thích hợp với phong cách dạy của mình. Không như bài viết này, khóa học là sự kết hợp giữa các bài viết, video, bài tập nhỏ và dự án lớn có thể xin việc được ngay. Học xong mình đảm bảo bạn sẽ lên tay ngay. 💪🏻