Javascript: sự phát triển của quản lí bất đồng bộ(asynchronous)

Javascript là một ngôn ngữ đơn luồng(single thread- chỉ thực hiện một việc tại một thời điểm)đồng bộ(synchronous). Nhưng quả thật khi phát triển một trang web chúng tôi luôn phải xử lý những tác vụ bất đồng bộ(như gọi AJAX để lấy dữ liệu từ backend, xử lí event trên web như click, mouse move, …) và đôi khi chúng ta cần đa luồng(dữ liệu lớn, ví dụ 10000 items trong một mảng gây ra hiện tượng crash trình duyệt, …)

Trong phạm vi bài viết này tôi chỉ tập trung phân tích vấn đề một ngôn ngữ sync xử lí tác vụ async như thế nào.

Hiện nay, chúng ta có một số cách để xử lí async trong javascript như sau:

– Sử dụng callback(ES5)

– Sự dụng Promise(ES6)

– Sử dụng cách mới async/await(ES7- chưa hỗ trợ nhiều trình duyệt)

Callback

Chúng ta có thể sử dụng một function làm tham số truyền vào cho một function khác. Sau đó một khoảng thời gian, function được truyền vào sẽ thực thi.

$(document).ready(function() {
$('#button').on('click', function(event) {
   $.getJSON('/data.json', function(data) {
     console.log(data);
   });
 });
});

Như chúng ta thấy có 3 callback cho document ready, button click event, getJSON. Tất cả là những function được truyền vào như những tham số.

Và còn hơn thế nữa, callback hell là đây:

getData(function(a) {
 getMoreData(function(b) {
  getMoreData(function(c) {
   getMoreData(function(d) {
    getMoreData(function(e) {
     // do something
    });
   });
  });
 });
});

Nhược điểm của callback: khó maintain, khó đọc, khó debug.

Và một anh hot boy đã ra đời để giải quyết những vấn đề của callback. Đó là Promise.

Promise

Với promise chúng ta quản lí async task qua 3 trạng thái: chờ(pending), hoàn thành(fulfilled), từ bỏ(rejected)

const promise = new Promise((resolve, reject) => {
  // do async stuff
  resolve('DONE!');
});

promise.then((result) => {
  console.log(result); // result will be 'DONE!'
});
const promise = new Promise((resolve, reject) => {
  // do async stuff
  reject(new Error('FAIL!'));
});

promise
  .then((result) => {
    // Will not be called
  })
  .catch((error) => {
    console.log(error); // FAIL!
  });

Bài toán callback hell được giải quyết như sau:

getData()
.then(getMoreData)
.then(getMoreData)
.then(getMoreData)
.then(getMoreData)
.then((result) => {
  //do something
})
.catch((error) => {
  handleError(error)
});
Chúng ta có thể xử lí các promise song song như sau:
Promise.all([
 firstAsyncCall(),
 secondAsyncCall(),
 lastAsyncCall(),
])
.then(result => {
  firstAsyncCallResult = result[0];
  secondAsyncCallResult = result[1];
  lastAsyncCallResult = result[2];
});

Ưu điểm của Promise là: dễ đọc, dễ debug, dễ maintain, cú pháp đẹp, có thể catch error dễ dàng, có hỗ trợ chaining promise(chuỗi các promise), xử lý các promise song song.

Async/Await

Hai từ  khóa này phải đi chung với nhau có nghĩa là: ta chỉ khai báo một await chỉ trong một async function.

async () => {
 const data = await getData();
 const body = document.querySelector('body');
 body.innerHTML = data;
}

Ta thấy code trên như một sync code có đúng không. Không còn then. Cool 🙂

Callback hell được viết lại bằng async/await như sau:

async function doGreatThing() {
try { 
 const firstData = await getData();
 const secondData = await getMoreData(firstData);
 const thirdData = await getMoreDate(secondData);
 // How about parallel call?
 const saveResults = await Promise.all([
   saveData(firstData), 
   saveData(secondData), 
   saveData(thirdData)]);
 } catch (error) {
   console.log(error);
 }
}

Tất cả trông như là sync.  Ưu điểm của asyn/await: dễ đọc, dễ hiểu, dễ maintain, không còn then, catch, xử lí error bằng try/catch, không còn callback nữa.

 

 

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *