🔥Đặt mua trước khóa học Node.js Super để được giảm lên đến 30%

Ôn tập callback, promise, async/await

🎉 Nếu anh em thấy Front-End cạnh tranh quá thì chúng ta học thêm Back-End thôi.

Mình đang hoàn thiện khóa học 🏆 Node.js Super, với các kiến thức như: Express.js, TypeScript, MongoDB, Socket.io, Docker, AWS, Swagger, ...

Vì đang hoàn thiện nên anh em có thể mua ngay từ bây giờ với mức giá sale off cực hấp dẫn😉

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à 😂.

js
// 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.

js
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ả.

js
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:

js
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á.

js
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à

bash
Promise created
Executing promise
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

js
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

js
// 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

js
// 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

js
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 đó

js
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ề

js
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

js
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
js
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.

js
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()

js
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é

js
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.

js
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.


Khóa học ReactJs giúp bạn chinh phục mức lương 25 - 30 triệu/tháng

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ể 😅

Chúng ta đều hiểu rằng Javascript và React không hề dễ, chúng có quá nhiều concept cần phải học. Mình cũng cảm thấy nó khó! Nay lại có thể Typescript nữa 🥲, thật sự khó nuốt.

Nhưng đừng lo: Bạn có thể nắm vững các kiến thức trên chỉ trong một khóa học ReactJs Super - Shopee Clone Typescript

Mình đã bắt đầu code React vào năm 2019, và nó đã trở thành thư viện ưa thích của mình để xây dựng UI và web app. Mình cũng đã làm việc với nhiều framework khác như Angular, Vue nhưng thực sự chỉ có React là đem lại cho mình cảm xúc và sự hiệu quả. 💓

Nếu bạn đang gặp khó khăn với React, mình ở đây để giúp bạn!

Mình đã dành hơn 6 tháng để phát triển khóa học ReactJs Super - Shopee Clone Typescript. Trong khóa này bạn sẽ được học mọi thứ về thư viện ReactJs, 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, quizz, 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. 💪🏻

Avatar Dư Thanh Được
Dư Thanh Được
Một developer thích nghiên cứu và chia sẻ kiến thức về lập trình, blockchain, marketing. Chuyên code và dạy lập trình website