async await giá như tôi đọc được bài viết này sớm hơn

Nội dung bài viết

Async await trong javascript đã có nhiều bài viết nhưng có lẽ đây là bài viết cuối cùng mà tôi muốn viết về Async await. Một lần làm cho rõ còn hơn lằng nhằng.


async/await


Tôi cá rằng lần đầu tiên các bạn nhìn thấy async/await chắc chắn nó không phải là ngôn ngữ javascript. Mà đó là của C# 5.0. Cho dù trong C # hay JavaScript, async / await là những tính năng tuyệt vời nhất mà tôi từng sử dụng, và chúng cũng là một cú pháp rất ngọt ngào nhất. 


Cũng giống như việc triển khai async / await của C# không thể tách rời khỏi các lớp Task, tương tự cũng như vậy việc triển khai async / await của JavaScript cũng không thể nào tách rời khỏi Promise trong javascript. OK, phần giới thiệu về lịch sử của async/await đã xong và xin mời các bạn mở qua phần thứ hai để tập trung vào nghiên cứu Async/await trong Javascript


Async là gì? await là gì?


Lần này làm cho ra nhẽ luôn. Đáng để đọc nó phải rõ ràng. Trước tiên thì hiểu theo nghĩa đen của nó, Async có nghĩa là "Không đồng bộ" còn await có nghĩa là "Chờ đợi". Chốt lại được hiểu nhanh như thế này, async được sử dụng để khai báo cho chúng ta biết đó là một hàm không đồng bộ. Và await được sử dụng để đợi một phương thức không đồng bộ hoàn thành. Và điều thú vị hơn nữa đó là việc chỉ sử dụng await trong hàm không đồng bộ mà thôi. Có nghĩa là:

async function A (){
    await B();
}

console.log(A())

async function B(){
    console.log("async2");
}

Ta thấy await B() chỉ hoạt động khi hàm khai báo có sử dụng async. Giả sử tôi xoá async đi thì xem có vấn đề gì không?

//xoá async 
function A (){
    await B();
}

console.log(A())

async function B(){
    console.log("async2");
}

Run và nhận thấy một lỗi : "Uncaught SyntaxError: await is only valid in async functions". Nhưng nó cũng có trường hợp ngoại lệ và sử dụng được như Top-level await. Vậy nếu có câu hỏi như thế này "Sử dụng hàm không đồng bộ mà không sử dụng await thì sao". Vậy vai trò của async là gì?

Vai trò của async trong javascript là gì?

Điều đó có nghĩa là như bên dưới:

async function A(){
    return "hello async";
}
const result = A();
console.log(result);

Mọi người ai cũng nghĩ chắc nó cũng return ra giá trị như những hàm khác mà thôi. Nhưng tôi đã chạy và thự tế nó khác, hãy xem:

AnonyStick$ node ping.js 
Promise { 'hello async' }

Vậy chính xác là nó trả về một Promise. Để giải thích điều này thì tôi có tìm thấy một tài liệu được việt hoá. Các bạn nên đón đọc, cho rõ. Chính vì hàm async trả về một đối tượng Promise, vì vậy khi lớp ngoài cùng không thể sử dụng await để nhận giá trị trả về của nó, tất nhiên chúng ta nên sử dụng phương thức gốc: then() để xử lý đối tượng Promise, như thế này:

A().then(v => {
    console.log(v);    // hello async
});

Nếu đến đây chúng ta không còn khó hiểu nữa, bởi vì chúng ta đã được học qua Promise javascript rồi. Không bỡ ngỡ nữa. Giờ việc tiếp theo chúng ta nói đến vai trò của await.

Vai trò của await trong javascript

Await được hiểu là nó đang chờ đợi một chức năng không đồng bộ hoàn thành, hay nói cách khác là await đang đứng hóng một biểu thức. Và kết quả tính toán của biểu thức này là một đối tượng Promise hoặc giá trị khác. 

Chú ý nè, nói như thế không có nghĩa là sử dụng await để hóng một kết quả là Promise, mà nó có thể hóng bất cứ một biểu thức nào, hay await sử dụng để lấy kết quả của một lệnh gọi hàm thông thường. Vì vậy, ví dụ sau có thể giúp bạn hình dung rõ hơn.

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test(); // "something", "hello async"

Nó đã quá rõ ràng cho việc giải thích ở trên. Cái hay của ví dụ là vậy, nó khác lý thuyết rất nhiều. Async await trong javascript Đến đây có thể nói rằng, khái niệm giải thích ở trên đã quá rõ về async/await. Vậy sử dụng Async await có lợi gì cho mỗi lập trình viên. Tôi có một ví dụ nhỏ nhỏ để so sánh cho các bạn xem nhé. Một ví dụ sử dụng promise và async await

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});

Nếu bạn sử dụng async/await thay vào đó, nó sẽ như thế này:

function takeLongTimeAsync() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTimeAsync();
    console.log(v);
}

test();

Tôi biết ví dụ trên không qua mặt được các bạn takeLongTime vì nó không sử dụng async và await. Trong thực tế, bản thân takeLongTime() đối tượng Promise được trả về. Và ở ví dụ tiếp theo chúng ta lại sử dụng async/await và cho ra một kết quả giống nhau. Vậy lợi ích ở đây là gì? 


Hơn nữa, nếu để ý kỹ hơn thì thấy, khi sử dụng async/await thì thấy code của chúng ta dài hơn thêm một chút. Vậy lợi thế của async/await cuối cùng nằm ở đâu? 


Ưu điểm của async/await


Trên kia là một câu hỏi thực sự hay, nếu như ví dụ trên thì tôi chả thấy lợi điểm của việc sử dụng async/await ở đâu hết. Vâng tôi sẽ trả lời nốt câu hỏi này một cách rõ nhất, không ăn không lấy tiền. Nhìn kỹ lại nếu lấy ví dụ trên để thực hiện thì đúng là async/await chảng có ưu điểm gì, nhưng nếu có một tình huống hay còn gọi là "promise chain javascript" thì lúc đó ra sẽ thấy được lợi điểm này. 


Cùng xem ví dụ - Giả sử rằng một doanh nghiệp được hoàn thành trong nhiều bước, mỗi bước là không đồng bộ và phụ thuộc vào kết quả của bước trước đó. Chúng tôi vẫn sử dụng setTimeout để mô phỏng hoạt động không đồng bộ:


function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

Bây giờ những bạn nào mà fan hâm mộ của Promise thực hiện đi nào:

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

Bây giờ tôi sử dụng async và await xem nó thế nào :

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

Kết quả giống khi sử dụng promise trước đó nhưng mã này trông rõ ràng hơn nhiều, rõ ràng đó là điểm khác biệt rồi.

Tóm lại


Sử dụng async/await là một phát minh tuyệt vời cho đến lúc này. Khi nào có hội sử dụng, tôi luôn dùng nó. Thế nhưng không loại trừ những sai lầm khi sử dụng async/await mà có nhiều bạn gặp phải, cho nên chúng ta phải chú ý. Ngoài ra phải phân biệt được các loại Promise nhưng promise.any(), promise.all(), hay Promise.race(). 


Tất cả những method đó, tôi đã nói rất rõ từng về cú pháp, cũng như là khi nào sử dụng chúng. Nếu có thời gian, thay vì lướt Fb thì qua đó đọc cho nó thêm phần cay đắng trong thế giới lập trình. Còn bây giờ các bạn cứ tìm kiếm cách sử dụng của async/await và promise trước đã. Trong tipjs này có rất nhiều bài viết hay lắm đấy. Có thể tham khảo ở đây.

Có thể bạn đã bị missing