ES6 ES7 Javascript cơ bản

Bất đồng bộ trong Javascript – Promise, Async/await

Trong bài này chúng ta sẽ tìm hiểu về Promise, Async/await để giải quyết các nhược điểm của Callback

I. Promise

Promise có nghĩa là một sự hứa hẹn hay lời hứa, mà lời hứa thì sẽ có hai trạng thái là hoàn thành và thất bại

Cách tạo ra 1 Promise

let promise = new Promise(/* executor */ (resolve, reject) => {
   // Thực thi các tác vụ bất đồng bộ ở đây
   // Gọi resolve(result) khi các tác vụ hoàn thành
   // Gọi reject(result) khi xảy ra lỗi
})

Tại mỗi thời điểm, Promise sẽ có những trại thái khác nhau, bắt đầu với pending hay unsetted. Trạng thái này chính là trạng thái ban đầu của Promise khi được khởi tạo và đang chờ kết quả trả về. Khi quá trình xử lý thực hiện xong xuôi, promise sẽ chuyển sang trạng thái setted, khi kết quả được trả về, sẽ có hai khả năng có thể xảy ra:

  • fulfilled: trạng thái xử lý thành công.
  • rejected: trạng thái xử lý thất bại.

Ví dụ

function hua_cho_vui() {
    return Promise((thuc_hien_loi_hua, that_hua) => {
        // Sau khi thi đại học xong
        // Nếu điểm bạn cao
        if (diem_cao) {
            // Lúc này trạng thái lời hứa là fulfilled
            thuc_hien_loi_hua("1000$");
        } else {
            // Lúc này trạng thái của lời hứa là rejected
            that_hua("1$")
        }
    });
}

// Khi vừa được khởi tạo xong, trạng thái của promise sẽ là pendding
// Mẹ vừa hứa với bạn xong, đang chờ điểm thi đại học của bạn
const promise = hua_cho_vui();
promise
    .then((li_xi_nhieu) => {
        ...
    })
    .catch((li_xi_it) => {
        ...
    });

Khi một promise được thực hiện, nếu thành công thì sẽ gọi callback trong hàm then, nếu thất bại thì promise sẽ gọi promise trong hàm catch

 

Promise chaining

Nếu xử lý các câu lệnh bất đồng bộ liên tiếp nhau với callback rất dễ dẫn đến tình trạng callback hell như mình đã nói ở phần trước khi mà có quá nhiều hàm callback bị lồng vào nhau làm cho việc đọc hiểu cũng như debug trở nên khó khăn. Promise chaining hay chuỗi promise được sinh tra nhằm khắc phục vấn đề trên.

Giá trị trả về của hàm then() sẽ là một promise khác, do đó có thể dùng promise để gọi liên tiếp các hàm bất đồng bộ. Promise thứ hai sẽ được xử lý khi promise thứ nhất trả về fulfilled hoặc reject.

1. Promise.all()

Promise.all() nhận và là một đối số và thông thường là một mảng các promise. Trạng thái của promise này sẽ là fulfilled nếu trạng thái của tất cả các promise được truyền vào là fulfilled, ngược lại promise sẽ mang trạng thái reject

Ví dụ

let promise_1 = new Promise ( (resolve, reject) => {
    resolve('Promise 1');
});

let promise_2 = new Promise ( (resolve, reject) => {
    resolve('Promise 2');
});

let promise_3 = new Promise ( (resolve, reject) => {
    resolve('Promise 3');
});

let promise = Promise.all([promise_1, promise_2, promise_3]);
promise.then ( result => {
    console.log(result);
}).catch( err => {
    console.log(err);    
})

Kết quả

[ ‘Promise 1’, ‘Promise 2’, ‘Promise 3’ ]

Promise.race()

Promise.race() sẽ xử lý promise đầu tiên có kết quả trả về không quan tâm kết quả trả về có lỗi hay không
Ví dụ

let promise_1 = new Promise ( (resolve, reject) => {
    resolve('Promise 1');
});

let promise_2 = new Promise ( (resolve, reject) => {
    resolve('Promise 2');
});

let promise_3 = new Promise ( (resolve, reject) => {
    resolve('Promise 3');
});

let promise = Promise.race([promise_1, promise_2, promise_3]);
promise.then ( result => {
    console.log(result);
}).catch( err => {
    console.log(err);    
})

Kết quả

Promise 1

II. Async/await (ES7)

Async/await là cơ chế giúp bạn thực thi các thao tác bất đồng bộ một cách tuần tự hơn , giúp đoạn code nhìn qua tưởng như đồng bộ nhưng thực ra lại là chạy bất đồng bộ, giúp chúng ta dễ hình dung hơn

1. Async

AsyncFunction luôn return về một Promise nếu trong code không trả về Promise nào thì sẽ có một Promise mới được resolve với giá trị return lúc đầu (kết quả sẽ trả về undefined nếu không có giá trị nào trong return). Vì kết quả trả về là promise nên bạn cũng có thể dùng cùng với callback như promise bình thường

async function getInfo() {
    return 'Demo Asyn Function';
};
getInfo().then(res => {
    console.log(res);
});

Kết quả: Demo Asyn Function

Nếu async trả về luôn một promise thì ta không cần resolve nữa

getUser = async () => Promise.resolve('User Info');
getUser().then(console.log);

Kết quả: User Info

2. Await

Về cơ bản thì await giúp cho cú pháp trông dễ hiểu hơn, thay thì phải dùng then() nối tiếp nhau thì chỉ cần đặt await trước mỗi hàm mà chúng ta muốn đợi kết quả của thao tác bất đồng bộ. Chỉ dùng được await trong hàm nào có async đứng phía trước

Giờ đây bạn đã có thể code bất đồng bộ dễ dàng hơn nhiều khi kết hợp giữa promise với callback, async, await cùng generator

Không có Promise thì await được xem như là 1 giá trị

(async function() {
   const x = await 1;
   console.log(x)
})();

Kết quả: 1

Các lệnh được code sau await luôn được chạy sau các đoạn code đồng bộ khác

(async function() { 
   console.log('A');
   const x = await 'B'; 
   console.log(x); 
})();
console.log('C');

Kết quả:

A
C
B

Có Promise: Hàm async đã bị tạm dừng khi gặp await và phải dừng 1 giây trước khi chạy tiếp để chờ kết quả được resolve từ Promise
(async function() {
   const promise_await = new Promise (resolve => setTimeout(() => console.log(1), 1000)); 
   await promise_await;
   //console.log('Done');
})();

Sau 1s in kết quả là 1

Tại sao nên sử dụng Async/Await?

  • Code ngắn và sạch hơn. Điều dễ thấy nhất khi dùng async/await số lượng code phải viết giảm đi đáng kể, không phải then rồi catch gì cả hay là đặt tên cho một biến mà ta không sử dụng, tránh được các khối code lồng nhau, code được viết ra như code chạy tuần tự dễ đọc hơn rất nhiều.
  • Công việc debug trở nên dễ dàng hơn vì so với Promise, mỗi một lần dùng await sẽ được tính là một dòng code.
  • Khi có lỗi, thay vì báo chung chung un-resolved như ở Promise thì exception sẽ chỉ ra là lỗi xảy ra ở dòng số bao nhiêu.

Leave a Comment