-->
ads here

Tìm hiểu về Callback, Promise và Async/Await trong Javascript

advertise here
Xin chào các bạn, bài viết của tháng 6 này sẽ là về một chủ đề rất thú vị trong Javascript, một ngôn ngữ rất là magic đó là Callback, Promise và Async/Await trong JS.
Nội dung chính của bài viết này gồm các phần như sau:
  1. Khái niệm về bất động bộ trong JS, khái niệm về Callback, Promise và async/await trong JS
  2. Ví dụ cụ thể.
Ok, giờ chúng ta bắt đầu với phần đầu tiên, khái niệm

1. Callback, Promise và Async/Await

Trước tiên chúng ta sẽ tìm hiểu xem lập trình đồng bộ và bất đồng bộ trong JS là gì?
Lập trình đồng bộ (Synchronous Programming) có thể hiểu như sau: khi chương trình gọi một hàm thực hiện một hành động trong thời gian dài nó chỉ trả về kết quả chỉ khi hành động đó kết thúc và trong lúc đó nó sẽ dừng toàn bộ chương trình lại cho đến khi hành động đó được thực hiện xong (Tức là các câu lệnh phía dưới hàm đó sẽ chưa được thực hiện)
Còn lập trình bất đồng bộ (Asynchronous Programming) cho phép nhiều thứ xảy ra cùng một lúc. Tương tự như trên, khi gọi một hàm thực hiện một hành động trong thời gian dài, chương trình không bị dừng mà tiếp tục chạy, khi hành động được thực hiện xong, chương trình sẽ được thông báo và nhận được kết quả trả về từ hành động đó  (ví dụ như: gọi API hay đọc dữ liệu từ disk)
Vậy trong Javascript, chúng ta xử lý bất đồng bộ bằng cách nào? Hiện tại có 3 hướng tiếp cận đó là Callback, Promise và Async/Await. Tiếp theo chúng ta sẽ lần lượt tìm hiểu từ khái niệm này:

Callback và Callback hell

Callback có thể hiểu là một hàm được truyền vào hàm khác như một tham số. Khi ta gọi một tác vụ bất đồng bộ để thực hiện một hành động dài, sau khi hành động đó được thực hiện xong, có kết quả, thì hàm callback sẽ được gọi.
Ví dụ như hàm setTimeout() trong JS:

 setTimeout(() => console.log("Tick"), 500);  

Hàm setTimeout gọi một tác vụ dài là "dừng 0.5s", sau khi hành động dại được thực hiện (đã dừng 0.5s) thì hàm callback là () => console.log("Tick") sẽ được thực hiện là in ra "
Hay một ví dụ khác như sau:

 fetchData(URL, (error, response) =>{  
  if (error){  
   // handle error  
   console.log(error)  
  }else{  
    // handle result  
    console.log("Fetched data: ", response)  
  }  
 })  

Hàm fetchData sẽ gửi request đến URL, sau đó tiếp tục thực hiện chương trình, đến khi có response trả về có thể là lỗi hay dữ liệu thì hàm callback sẽ được gọi để xử lý kết quả trả về.
Hàm fetchData có thể được viết lại như sau, trong đó hàm handleResponse chính là callback

 handleResponse = (error, response) =>{  
  if (error){  
   // handle error  
   console.log(error)  
  }else{  
    // handle result  
    console.log("Fetched data: ", response)  
  }  
 }  
 fetchData(URL,handleResponse )  

Callback hell: callback là cách tiếp cận khá đơn giản khi xử lý bất đồng bộ, tuy nhiên hướng tiếp cận này sẽ gặp phải vấn đề là nếu các callback được gọi liên tục lồng nhau thì sẽ gặp phải hiện tượng callback hell như sau:

const verifyUser = function(username, password, callback){  
   dataBase.verifyUser(username, password, (error, userInfo) => {  
     if (error) {  
       callback(error)  
     }else{  
       dataBase.getRoles(username, (error, roles) => {  
         if (error){  
           callback(error)  
         }else {  
           dataBase.logAccess(username, (error) => {  
             if (error){  
               callback(error);  
             }else{  
               callback(null, userInfo, roles);  
             }  
           })  
         }  
       })  
     }  
   })  
 }; 

Hàm verifyUser thực hiện các tác vụ là xác thực user, lấy role của user  đó và ghi lại thời gian truy cập của user. Ta có thể thấy nếu việc thực hiện các callback liên tục thì code chúng ta sẽ  có dạng như sau gây cho việc đọc code rất khó khăn


Và để khắc phục nhược điểm của callback hell, chúng ta sẽ đi qua 2 hướng tiếp cận khác để xử lý bất đồng bộ đó là Promise và Async/Await

Promise

Promise là khái niệm xuất hiện ở ES2015 hay ES6 dùng để xử lý bất đồng bộ tương tự callback nhưng giúp ta tránh được hiện tượng callback hell ở trên. Promise dùng để xử lý bất đồng bộ của một tác vụ.
Một promise có 3 trạng thái:
  • Pending: Trạng thái khởi tạo của một promise khi bắt đầu tác vụ bất đồng bộ
  • Fulfilled: Tác vụ kết thúc, tác vụ thực hiện thành công và trả về giá trị
  • Rejected: Tác vụ kết thúc, tác vụ thực hiện thất bại, trả về Error object
Để tạo ra một promise ta dùng keyword new và kèm với nó là 2 hàm callback "resolve" và "reject" để xử lý kết quả trả về cũng như lỗi:

const condition = true;  
 const promise = new Promise((resolve, reject) => {  
  if(condition){  
    const returnData = "success"  
    resolve(returnData)  
  } else {  
   reject(new Error("Failed"))  
  }  
 })  

Sử dụng promise bằng cách sử dụng hàm "then" và "catch"

 promise  
  .then(result => {  
   // handle asynchronous result  
  })  
  .catch(error => {  
   // handle error  
  })  

Tiếp theo chúng ta sẽ điểm qua một hướng tiếp cận việc xử lý bất đồng bộ khác là asyn/await

Asyn/Await

Asyn/await là bước tiến hoá tiếp theo trong việc xử lý bất đồng bộ trong JS, được giới thiệu trong ES2017.
Async function là hàm trả về một promise. Nếu hàm trả về giá trị thì promise sẽ resolve với giá trị đó, nếu hàm async trả về lỗi thì promise sẽ reject. Ta xét ví dụ sau:

 const asynFunc = async () => {  
  return "This is async function"  
 }  
 // tương đương với  
 const promiseFunc = () {  
  return Promise.resolve("This is async function")  
 }  



 const asynFunc = async () => {  
  throw 100  
}
 // tương đương với  
 const promiseFunc = () {  
  return Promise.reject(100)  
 }  

Await là từ khoá được sử dụng bên trong hàm async để đảm bảo tất cả các promise bên trong async function trở nên đồng bộ.

 const doSomething = async (value) => {  
  // hàm fetchData là hàm bất đồng bộ  
  let result = await fetchData(URL, value)  
  return result  
 } 

Sau khi đã tìm hiểu về các định nghĩa, chúng ta sẽ đến ví dụ việc sử dụng callback, promise và async/await

2. Ví dụ cụ thể:

Ở ví dụ này, ta sẽ thực hiện một đoạn code xử lý bất đồng bộ với 3 hướng tiếp cận là Callback, Promise và Async/Await. Ta lấy ví dụ về một hàm có chức năng sau:

  1. Xác thực username và password của người dùng
  2. Lấy vai trò của người dùng đó trong hệ thống
  3. Log lại thời gian truy cập của người dùng đó

Dùng callback

 const verifyUser = (username, password, callback) =>{  
  database.verifyUser(username,password, (err, userData) => {  
   if(err){  
    callback(err)  
   }else {  
    database.getRoles(username, (err, roles) => {  
     if(err){  
      callback(err)  
     }else {  
      database.logging(username, err => {  
       if(err){  
        callback(err)  
       }else{  
        callback(null,userData,roles)  
       }  
      })  
     }  
    })  
   }  
  })  
 }  
 /**  
  * Implementation for other functions  
  */  
 // verifyUser  
 const verifyUser = (username, password, cb) => {  
  let result = databaseConnection.verifyQuery(username, password) // This return userData  
  cb(result)  
 }  
 // getRoles  
 const getRoles = (username, cb) => {  
  let result = databaseConnection.getRolesQuery(username) // This return userData  
  cb(result)  
 }  
 // logging  
 const loggin = (username, cb) => {  
  let result = logAccessTime(username) // This return userData  
  cb(result)  
 }  

Như ta thấy, đã xuất hiện hiện tượng callback hell

Dùng Promise

 const verifyUser = (username, password) => {  
  database.verifyUser(username,password)  
     .then(userInfo => database.getRoles(userInfo))  
     .then(rolesInfo => database.logging(rolesInfo))  
     .then(finalResult => {  
      //do something  
     })  
     .catch(err => {  
      // error handler  
     })  
 }  
 /**  
  * Implementation for other functions  
  */  
 // verifyUser  
 const verifyUser = (username,password) => {  
  return new Promise((resolve, reject) => {  
   database.verifyQuery(username,password)  
      .then(result => resolve(result))  
      .catch(reject)  
  })  
 }  
 // getRoles  
 const getRoles = (userInfo) => {  
  return new Promise((resolve, reject) => {  
   database.getRolesQuery(userInfo.username)  
      .then(result => resolve(result))  
      .catch(reject)  
  })  
 }  
 // logging  
 const logging = (rolesInfo) => {  
  return new Promise((resolve, reject) => {  
   database.logging(rolesInfo.username, rolesInfo.role)  
      .then(result => resolve(result))  
      .catch(reject)  
  })  
 }  

Dùng Async/Await

 const verifyUser = async (username, password) => {  
  try{  
   let userInfo = await database.verifyUser(username, password)  
   let rolesInfo = await database.getRoles(userInfo)  
   let logStatus = await database.logging(rolesInfo)  
   return logStatus  
  }catch(err){  
   // error handler  
  }  
 }  

Kết

Bài viết này mình viết và tham khảo trên mạng dựa trên hiểu biết cá nhân, nếu có chỗ nào không hợp lý thì mong mọi người để lại comment trao đổi mình sửa và để có những bài viết chất lượng hơn về sau, xin cảm ơn!

Reference


Advertisement
COMMENTS ()