Từ callback đến promise – async await trong javascript

Gần đây, một dự án java mà tôi đang thực hiện cùng team đã được releases. Vì vậy đây là thời gian mà các member trong team có thời gian để xả stress cũng như giải trí sau những ngày ăn ngủ với dự án (mình chém gió thôi, chứ team mình làm rất ít phải OT ^^, sẵn tiện anh em nào trên 3 năm kinh nghiệm java ứng tuyển vào team mình cho vui nhé ^_^). Riêng với mình thì đây là lúc mình có thời gian để tìm hiểu và hiểu sâu hơn với javascript. Bản chất javascript là một ngôn ngữ mình không thích các mã code bởi vì nó không tường mình kiểu như java hay php…Nhưng sắp tới mình cũng có một dự án freelacer cũng như bỗ trợ kiến thức nên sẽ tìm hiểu sâu hơn với javascript. Có một điều là càng tìm hiểu mình càng thấy javascript cực kỳ thú vị. Kiểu như một người con gái ban đầu với cái nhìn đầu tiên không thích nhưng khi tiếp cận rồi thì ghiền hẳn ra 😀

OK! Mình có tính hay nói luyên thuyên, các bạn thông cảm nhé. Trong bài này mình chia sẻ theo quan điểm cá nhân và những gì mình tìm hiểu cũng như làm các dự án mình sẽ chia sẻ một vấn đề khá thú vị, rất hay là xử lý bất đồng bộ trong javascript. Bản chất của javascript là một ngôn ngữ lập trình bất đồng bộ Tuy nhiên, trong một số trường hợp các bạn mong muốn xử lý đồng bộ, nghĩa là ta có 3 công việc, công việc a xong mới đến công việc b xử lý, sau đó đến công việc c.

Vậy làm sao để mình giải quyết vấn đề trên, lúc này ta xử lý bất đồng bộ trong java bằng cách sử dụng callback đến promise – async await trong javascript nhé.

Mình sẽ đi lần lượt các cách xử lý khác nhau:

Callback huyền thoại….

Ta có trường hợp như sau:

Trong một chức năng, tôi mong muốn tạo một hàm xử lý lấy userId của một người dùng thông qua username của người dùng đó đang đăng nhập. Từ đó khi có userId ta sẽ lấy được postId (bài viết mà người dùng đã đăng) dựa vào userId, cuố cùng kho có postId tôi sẽ xử lý để lấy được commentId của bài viết đó.

Từ callback đến promise - async await trong javascript
Từ callback đến promise – async await trong javascript

 

Đây là đoạn mã xử lý mô phỏng các chương trình trên:

function getUserByUsername(username) {
	setTimeout(function() {
		////query database ...
		let userId = 1;
		console.log('find userId search is  :'+userId);
		return userId;
	}, 3000);
}

function getPostByUserId(userId) {
	
	setTimeout(function() {
		//query database ...
		let postId = 2;
		console.log('find postId search is  :'+postId);
		return postId;
	}, 2000);
}

function getPostCommentByPostId(postId) {
	setTimeout(function() {
		//query database ...
		let commentId = 3;
		console.log('find commentId search is  :'+commentId);
		return commentId;
	}, 1000);
}

var userId = getUserByUsername('phut');
var postId = getPostByUserId(userId);
var commentId = getPostCommentByPostId(postId);







 

Sau khi chạy đoạn mã trên thì kết quả in ra màn hình console như sau:

Kết quả mô phỏng
Kết quả mô phỏng

Như các banh thấy, vì thời gian timeout của các hàm là hoàn toàn khác nhau. Với hàm để lấy được commentId thì mất đến 3s, hàm lất postId thì mất đến 2s và hàm lấy userId mất 1s. Ở đây tôi đang mô phỏng cho giá trị timeout nó tương ứng sau này truy vấn đến database đôi khi sẽ có thời gian như thế. Vậy trường hợp ở trên đã xử lý bất động bộ rằng ta mong muốn các hàm phải chạy theo thứ tự, tuy nhiên với đặc tính của javascript thì nó đang bất đồng bộ ơ đây.

Để giải quyết vấn đề này, tôi sử dụng callback trong javascript như sau:

function getUserByUsername(username, callback) {
	setTimeout(function() {
		userId = 1;
		console.log('find userId search is  :'+userId);
		return callback(null, userId);
	}, 3000);
}

function getPostByUserId(userId, callback) {
	
	setTimeout(function() {
		var postId = 2;
		console.log('find postId search is  :'+postId);
		return callback(null, postId);
	}, 2000);
}

function getCommentByPostId(postId) {
	
	setTimeout(function() {
		var commentId = 3;
		console.log('find commentId search is  :'+commentId);
		return commentId;
	}, 2000);
}

getUserByUsername('phut', function(err, userId) {
	 getPostByUserId(userId, function(err, postId){
		 getCommentByPostId(postId);
	 });
 });


Kết quả nhận được trên màn hình console như sau:

Kết quả khi xử lý callback trong javascript
Kết quả khi xử lý callback trong javascript

Như vậy, với cách xử lý callback, ta đã xử lý được vấn đề bất đồng bộ ở đây, tuy nhiên nếu nói về kết quả thì hoàn toàn chính xác, nó chạy theo thứ tự các hàm mà ta gọi. Nhưng trong trường hợp này khi code callback này ta dẫn đến trường hợp mà các lập trình viên như mình không ưa thích cho lắm đó chính là callback hell, các bạn có thể tham khảo chi tiết callback hell tại đây. 

Đại ý là các bạn để ý trường hợp mình mô tả code ở trên, nếu bây giờ ta xử lý tiếp, ví dự như khi có post thì đếm số lượt đánh giá, rồi đánh giá đó bời userId nào…như vậy code của ta muốn xử lý bất đồng bộ  thì cứ phải loằng ngoằng đi vào đi vào mỗi lúc càng sâu như vực thẳm.

getUserByUsername('phut', function(err, userId) {
	 getPostByUserId(userId, function(err, postId){
		 getCommentByPostId(postId, function(err, postId){
			getRateByPostId(rateId, function(err, postId){
				getUserByRateId(rateId);
			});
		});
	 });
});

Như vậy, nếu càng xử lý thì code chúng ta cực kỳ khó maintain, khó hiểu, thì đây người ta gọi là callback con heo :)), nghĩa là cứ gọi callback trả về trong hàm rồi trong hàm , trong hàm….(nghẹt thở :(( )

Sử dụng Promise…

Từ đó, ở ES6, các developer pro trên thế giới người ta code ra Promise trong ES6 (Bản chất ES6 cũng là javascript nha các bạn, và es6 có đuôi là ts, dùng lệnh để xử lý mà nó biên dịch thành file js để chạy code js) để xử lý vấn đề trên (callback hell) ta sử dụng Promise. Promise  là lời hứa, lời hứa ở đây kiểu như tôi sẽ thực hiện xong, ok thực hiện xong rồi ông khác mới được xử lý. Ta có thể hiểu nôm na như vậy.

Vậy với Promise cho phép ta trả về một Promise, và trong promise này ta có 2 tham số truyền vào resolve và reject.

resolve; nghĩa là khi thực hiện ta gọi hàm resolve  để truyền giá trị trả về.

reject: nghãi là khi bị lỗi thì ta gọi hàm reject để trả kết quả lổi về để xử lý tiếp theo.

Nếu các bạn muốn tìm hiểu nhiều hơn về promise, mình sẽ để link bên dưới bài viết để các bạn study, nếu có thời gian. mình sẽ viết bài cách xử lý promise trong es6 nhé cũng như trong nodejs.

var getUserIdByUsername = (username) => { return new Promise(function(resolve, reject) {
		setTimeout(function() {
			if(typeof username == 'number') { 
				//console.log(typeof username);
				return reject('error occurred with function getUserIdByUsername!');
			}
			var userId = 1;
			console.log('find user id search is  :'+userId);
			resolve(userId);
		}, 1000);
	})
}; 

var getPostIdByUserId = (userId) => { return new Promise(function(resolve, reject) {
		setTimeout(function() {
			if(typeof userId != 'number') { 
				return reject('error occurred with function getPostIdByUserId!');
			}
			var postId = 2;
			console.log('find pos id search is  :'+postId);
			resolve(postId);
		}, 2000);
	})
};

var getCommentIdByPostId = (postId) => { return new Promise(function(resolve, reject) {
		setTimeout(function() {
			if(typeof postId != 'number') { 
				return reject('error occurred with function getCommentIdByPostId!');
			}
			var commentId = 3;
			console.log('find comnent id search is  :'+commentId);
			resolve(commentId);
		}, 3000);
	})
};

getUserIdByUsername('phutvinaenter')
.then((userId) => getPostIdByUserId(userId))
.then((postId) => getCommentIdByPostId(postId))
.then((commentId) => {
	// continue promise
})
.catch((error) => {
	console.log(error);
});





Ta thấy rằng khi xử lý với promise thì code chúng ta đã trong sáng hơn nhiều.Đầu tiên gọi hàm cần xử lý sau đó, khi có thực hiện mỗi hàm xong để nhận giá trị trả về thì ta xử lý trong then, catch là nơi xử lý lỗi khi xảy exception ra một trong các hàm mà ta xử lý trên.

Với promise ta thấy rằng code dễ maintain nhiều hơn so với khi sử dụng callback. Tuy nhiên nó chỉ ở mức dễ maintain còn lại nếu có nhiều trường hợp thêm, ta thấy tằng nó cũng gần tương tự như callback hell mà thôi, nhưng bây giờ thì code nằm trên cùng dòng nhìn rõ ràng hơn thôi nhỉ.

Để giải quyết vấn đề đó, ở phiên bản ES7, 1 khái niệm với 2 từ khóa mới được đưa vào là hàm async/ awiat.  Hàm async cho phép ta viết các thao tác bất đồng bộ khi gọi các hàm sử dụng nên code sẽ dễ đọc, dễ hiểu và dễ maintain hơn rất nhiều!

Async / awiat trong ES7 có làm hài lòng bạn? 

Hiện nay, như mình tìm hiểu, study thì Async / awiat đang được các lập trình viên trên thế giới xử lý rất nhiều. Ta cùng xử lý Async/awiat với cách mô tả các function trên như sau:

let getUserIdByUsername = (username) => { return new Promise(function(resolve, reject) {
		setTimeout(function() {
			if(typeof username == 'number') { 
				//console.log(typeof username);
				return reject('error occurred with function getUserIdByUsername!');
			}
			let userId = 1;
			console.log('find user id search is  :'+userId);
			return resolve(userId);
		}, 1000);
	})
}; 

let getPostIdByUserId = (userId) => { return new Promise(function(resolve, reject) {
		setTimeout(function() {
			if(typeof userId != 'number') { 
				return reject('error occurred with function getPostIdByUserId!');
			}
			let postId = 2;
			console.log('find pos id search is  :'+postId);
			resolve(postId);
		}, 2000);
	})
};

let getCommentIdByPostId = (postId) => { return new Promise(function(resolve, reject) {
		setTimeout(function() {
			if(typeof postId != 'number') { 
				return reject('error occurred with function getCommentIdByPostId!');
			}
			let commentId = 3;
			console.log('find comnent id search is  :'+commentId);
			resolve(commentId);
		}, 3000);
	})
};
(async () => {
	try {
		let userId = await getUserIdByUsername('phutranit');
		console.log("userId : "+userId);
		let postId = await getPostIdByUserId(userId);
		console.log("postId : "+postId);
		let commentId = await getCommentIdByPostId(postId);
		console.log("commentId : "+commentId);
	} catch(error) {
		console.log(error);
	}
})();

Để sử dụng hàm async, ta cần khai báo từ khóa async ngay trước từ khóa định nghĩa hàm. Tức là, với hàm định nghĩa với từ khóa function ta phải khai báo ngay trước function, với hàm mũi tên (arrow function) ta phải khai báo trước tập tham số đầu vào.

Ý thông báo với javascript rằng ta đang xủ lý bất đồng bộ.

Với từ khóa async, ta có thể đợi các Promise (thao tác bất đồng bộ) xử lý trong hàm đó mà không tạm dừng luồng chính bằng từ khóa await như ở ví dụ trên.

Kết quả trả ra của hàm async luôn là một Promise dù bạn có gọi await – có xử lý bất đồng bộ hay không. Promise này sẽ ở trạng thái thành công với kết quả được trả ra với từ khóa return của hàm async, hoặc trạng thái thất bại với kết quả được đẩy qua từ khóa throw trong hàm async.

Như vậy, bản chất của hàm async chính là Promise.

Với Promise, ta có thể xử lý ngoại lệ với catch khá đơn giản.Vậy với hàm async cũng xử lý việc nhận lỗi một cách khá đơn giản mà các bạn code java hay php quen thuộc là dùng try/catch, việc này cực kì đơn giản bằng từ khóa try catch hệt như các thao tác đồng bộ.

Vậy, kết quả trả về như sau:

Xử lý bất đồng bộ bằng async/await
Xử lý bất đồng bộ bằng async/await

Kết quả vẫn không làm ta thất vọng với kết quả hoàn toàn chính xác.

Trong bài viết này mình cũng đã phân tích cho các bạn hiểu hơn về callback ,promise – async await trong javascript.Như các bạn tháy rằng bản chất khi ta dùng async cho hàm thì hàm đó sẽ trả về là một promise. Một lưu ý rằng ta không thể sử dụngawait để để thực hiện callback khi không khai báo async trên các hàm cần xử lý callback.Ta cũng lưu ý rằng không nên làm dụng về async/await bởi vì nó bất đi tính chất chạy song song của chương trình, mà đây là ưu điểm của javascript nói riêng và nodejs nói chung. Ta chri sử dụng async await khi mong muốn giải quyết bất đồng bộ!

Xin chào các bạn!

Happy coding!

0 0 đánh giá
Đánh giá bài viết
Theo dõi
Thông báo của
guest
0 Góp ý
Phản hồi nội tuyến
Xem tất cả bình luận
x