Hướng dẫn sử dụng ECMAScript Promise, Async Await

Xem thêm các chuyên mục:

1- Promise là gì?

Trong lập trình máy tính bạn rất thường xuyên làm việc với các hàm (function), và khi gọi một hàm bạn gần như ngay lập tức nhận được kết quả trả về.
function sum(a, b)  {
  return a + b;
}

// Call function:
let result = sum(10, 5);

console.log(result);
 
Giả sử bạn tạo ra một hàm có tên downloadFile(url) để download một tập tin từ Internet.
Việc download một tập tin lớn có thể mất vài phút hoặc lâu hơn. Khi gọi hàm downloadFile(url) một cách đồng bộ (synchronously) nó sẽ đóng băng mọi thao tác của người dùng cho tới khi nó hoàn thành, như vậy trong khoảng thời gian tập tin đang được download người dùng không thể thao tác với ứng dụng.
Vì vậy bạn không thể trông đợi một hàm giống như sau:
// Call download:

var myfile = downloadFile("http://example.com/file.mp3");

Promise?

  • Câu trả lời là hàm này nên trả về một Promise (Lời hứa) thay vì một tập tin.
Hãy tưởng tượng, cô bạn gái xinh đẹp của bạn nói với bạn rằng "Hãy cưới em!". Okey, tất nhiên rồi, nhưng bạn không thể làm điều đó ngay lập tức vì bạn cần thêm thời gian để chuẩn bị, nhưng cô ấy đang chờ câu trả lời của bạn, và tốt nhất bạn nên trao cho cô ấy một lời hứa, và lập tức cả hai đều vui vẻ.

Promise State?

Một Promise có 3 trạng thái:
Trạng thái Mô tả
Pending Lời hứa đang trong quá trình thực hiện, bạn không thể biết nó sẽ thành công hay thất bại. Chẳng hạn, tập tin đang được download.
Fulfilled Nhiệm vụ của lời hứa đã hoàn thành. Chẳng hạn, tập tin đã được download thành công.
Rejected Nhiệm vụ của lời hứa bị thất bại. Chẳng hạn, có một lỗi gì đó xẩy ra trong quá trình download tập tin.
Cú pháp tạo một đối tượng Promise:
new Promise (
      function (resolve, reject) {
           // Codes
      }
);
Ví dụ:
// [ECMAScript 5 Syntax]

var isNetworkOK = true;

// A Promise
var willIGetAFile = new Promise (
    function (resolve, reject) {

        if (isNetworkOK) {
            console.log("Complete the download process!"); // ***
            var file = {
                fileName: 'file.mp3',
                fileContent: '...',
                fileSize: '3 MB'
            };
            resolve(file); // fulfilled
        } else {
            console.log("File download process failed!"); // ***
            var error = new Error('There is a problem with the network.');
            reject(error); // reject
        }

    }
);
Hàm downloadFile(url) của bạn sẽ trả về một đối tượng Promise, giống như sau:
// [ECMAScript 5 Syntax]

var isNetworkOK = true;

// This function return a Promise
function downloadFile(url)  {
    console.log("Start downloading file ..."); // ***

    // A Promise
    var willIGetAFile = new Promise (
        function (resolve, reject) {

            if (isNetworkOK) {
                console.log("Complete the download process!"); // ***
                var file = {
                    fileName: 'file.mp3',
                    fileContent: '...',
                    fileSize: '3 MB'
                };
                resolve(file); // fulfilled
            } else {
                console.log("File download process failed!"); // ***
                var error = new Error('There is a problem with the network.');
                reject(error); // reject
            }

        }
    );

    return willIGetAFile; // Return a Promise.
}

2- Sử dụng Promise

Ở phần trên chúng ta đã có hàm downloadFile(url), hàm này trả về một đối tượng Promise, bây giờ chúng ta sẽ tìm hiểu xem nào thế nào để sử dụng hàm này.
// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3");


willIGetAFile
        .then(function (fulfilled) {
            // Get a File
            // Output: {fileName: 'file.mp3', fileContent: '...', fileSize: '3 MB'}
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });
OK, Đây là code đầy đủ của ví dụ này:
promise-example.js
// [ECMAScript 5 Syntax]

var isNetworkOK = true;

// This function return a Promise
function downloadFile(url)  {
    console.log("Start downloading file ..."); // ***

    // A Promise
    var willIGetAFile = new Promise (
        function (resolve, reject) {

            if (isNetworkOK) {
                console.log("Complete the download process!"); // ***
                var file = {
                    fileName: 'file.mp3',
                    fileContent: '...',
                    fileSize: '3 MB'
                };
                resolve(file); // fulfilled
            } else {
                console.log("File download process failed!"); // ***
                var error = new Error('There is a problem with the network.');
                reject(error); // reject
            }

        }
    );

    return willIGetAFile; // Return a Promise.
}

// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3");


willIGetAFile
        .then(function (fulfilled) {
            // Get a File
            // Output: {fileName: 'file.mp3', fileContent: '...', fileSize: '3 MB'}
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });
 
Nếu isNetworkOK = true, khi chạy ví dụ bạn nhận được kết quả:
Nếu isNetworkOK = false, khi chạy ví dụ bạn nhận được kết quả:

3- Chaining Promise

Chaining Promise (Chuỗi lời hứa) là một tập các Promise liên tiếp được xích lại với nhau.
Hãy tưởng tượng bạn cần thực hiện 2 thao tác bao gồm, gọi hàm downloadFile(url) để download một tập tin từ Internet, sau đó gọi hàm openFile(file) để mở tập tin vừa download được.
Việc download một tập tin từ Internet tiêu tốn một khoảng thời gian, hàm downloadFile(url) được thiết kế trả về cho bạn một lời hứa ( Promise). Bạn chỉ có thể có tập tin để mở nếu việc download là thành công. Vì vậy bạn cũng nên thiết kế hàm openFile(file) trả về một lời hứa ( Promise).
Viết hàm openFile(file) trả về một đối tượng Promise.
function openFile(file) {
    console.log("Start opening file ..."); // ***

    var willFileOpen = new Promise(
        function (resolve, reject) {
             var message = "File " + file.fileName + " opened!"
             resolve(message);
        }
    );

    return willFileOpen; // Return a Promise.
}
Thay vì tạo ra một đối tượng Promise thông qua toán tử new, bạn có thể sử dụng phương thức tĩnh Promise.resolve(value) hoặc Promise.reject(error), 2 phương thức này trả về 1 đối tượng Promise.
Bạn có thể viết lại hàm openFile(file) ở trên một cách ngắn gọn hơn:
// Shorter:

function openFile(file) {
    console.log("Start opening file ..."); // ***
    
    var message = "File " + file.fileName + " opened!"

    // Create a Promise
    var willFileOpen = Promise.resolve(message);
    return willFileOpen;
}

 
Gọi hàm downloadFile(url)openFile(file) theo phong cách Promise:
console.log("Start app.."); // ***

// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3");


willIGetAFile
        .then(openFile) // Chain it!
        .then(function (fulfilled) { // If successful fileOpen.
            // Get a message after file opened!
            // Output: File file.mp3 opened!
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });


console.log("End app.."); // ***
Xem code đầy đủ của ví dụ:
chaining-promise-example.js
// [ECMAScript 5 Syntax]

var isNetworkOK = true;

// This function return a Promise
function downloadFile(url)  {
    console.log("Start downloading file ..."); // ***

    // A Promise
    var willIGetAFile = new Promise (
        function (resolve, reject) {

            if (isNetworkOK) {
                console.log("Complete the download process!"); // ***
                var file = {
                    fileName: 'file.mp3',
                    fileContent: '...',
                    fileSize: '3 MB'
                };
                resolve(file); // fulfilled
            } else {
                console.log("File download process failed!"); // ***
                var error = new Error('There is a problem with the network.');
                reject(error); // reject
            }

        }
    );

    return willIGetAFile; // Return a Promise.
}

function openFile(file) {
    console.log("Start opening file ..."); // ***

    var willFileOpen = new Promise(
        function (resolve, reject) {
             var message = "File " + file.fileName + " opened!"
             resolve(message);
        }
    );

    return willFileOpen; // Return a Promise.
}

// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3");


willIGetAFile
        .then(openFile) // Chain it!
        .then(function (fulfilled) { // If successful fileOpen.
            // Get a message after file opened!
            // Output: File file.mp3 opened!
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });
 
Chạy ví dụ và nhận được kết quả:

4- Promise là không đồng bộ

Promise là không đồng bộ (Asynchronous). Điều đó có nghĩa là khi bạn gọi một hàm Promise (Hàm trả về 1 Promise) nó sẽ không đóng băng ứng dụng của bạn trong quá trình lời hứa đang được thực hiện.
Để làm rõ vấn đề này chúng ta sửa lại ví dụ ở trên một chút. Sử dụng hàm setTimeout() để mô phỏng việc download tập tin sẽ bị tốn một khoảng thời gian nào đó.
chaining-promise-example-2.js
// [ECMAScript 5 Syntax]

var isNetworkOK = true;

// This function return a Promise
function downloadFile(url)  {
    console.log("Start downloading file ..."); // ***

    // A Promise
    var willIGetAFile = new Promise (
        function (resolve, reject) {

            if (isNetworkOK) {
                setTimeout( function() {
                    console.log("Complete the download process!"); // ***
                    var file = {
                        fileName: 'file.mp3',
                        fileContent: '...',
                        fileSize: '3 MB'
                    };
                    resolve(file); // fulfilled
                }, 5 * 1000); // 5 Seconds
            } else {
                var error = new Error('There is a problem with the network.');
                reject(error); // reject
            }

        }
    );

    return willIGetAFile; // Return a Promise.
}


function openFile(file) {
    console.log("Start opening file ..."); // ***

    var willFileOpen = new Promise(
        function (resolve, reject) {
             var message = "File " + file.fileName + " opened!"
             resolve(message);
        }
    );

    return willFileOpen; // Return a Promise.
}

console.log("Start app.."); // ***

// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3");


willIGetAFile
        .then(openFile) // Chain it!
        .then(function (fulfilled) { // If successful fileOpen.
            // Get a message after file opened!
            // Output: File file.mp3 opened!
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });

console.log("End app.."); // ***
 
 
Kết quả mà bạn nhận được khi chạy ví dụ trên:
Hãy chú ý tới thứ tự các message được in ra trên màn hình Console:
Start app..
Start downloading file ...
End app..
Complete the download process!
Start opening file ...
File file.mp3 opened!

5- Promise trong ES6

ECMAScript-6 đưa vào cú pháp hàm mũi tên (Narrow Function), vì vậy bạn có thể viết lại ví dụ trên theo cú pháp ES6:
chaining-promise-es6-example.js
// [ECMAScript 6 Syntax]

var isNetworkOK = true;

// This function return a Promise
downloadFile = function(url)  {
    console.log("Start downloading file ..."); // ***

    // A Promise
    var willIGetAFile = new Promise (
       (resolve, reject) => {

           if (isNetworkOK) {
               setTimeout( function() {
                   console.log("Complete the download process!"); // ***
                   var file = {
                       fileName: 'file.mp3',
                       fileContent: '...',
                       fileSize: '3 MB'
                   };
                   resolve(file); // fulfilled
               }, 5 * 1000); // 5 Seconds
           } else {
               var error = new Error('There is a problem with the network.');
               reject(error); // reject
           }
        }
    );

    return willIGetAFile; // Return a Promise.
}

openFile = function (file) {
    console.log("Start opening file ..."); // ***

    var willFileOpen = new Promise(
       (resolve, reject) => {
             var message = "File " + file.fileName + " opened!"
             resolve(message);
        }
    );

    return willFileOpen; // Return a Promise.
}


console.log("Start app.."); // ***

// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3");


willIGetAFile
        .then(openFile) // Chain it!
        .then(function (fulfilled) { // If successful fileOpen.
            // Get a message after file opened!
            // Output: File file.mp3 opened!
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });

console.log("End app.."); // ***

 
Xem thêm về hàm và hàm mũi tên trong ECMAScript:

6- ES7 - Async Await

ECMAScript-7 giới thiệu cú pháp asyncawait, chúng giúp cho việc sử dụng Promise dễ dàng hơn, và dễ hiểu hơn.
chaining-promise-es7-example.js
// [ECMAScript 7 Syntax]

var isNetworkOK = true;

// An Asynchronous function return a Promise
async function downloadFile(url)  {
    console.log("Start downloading file ..."); // ***

    // A Promise
    var willIGetAFile = new Promise (
       (resolve, reject) => {

           if (isNetworkOK) {
               setTimeout( function() {
                   console.log("Complete the download process!"); // ***
                   var file = {
                       fileName: 'file.mp3',
                       fileContent: '...',
                       fileSize: '3 MB'
                   };
                   resolve(file); // fulfilled
               }, 5 * 1000); // 5 Seconds
           } else {
               var error = new Error('There is a problem with the network.');
               reject(error); // reject
           }
        }
    );

    return willIGetAFile; // Return a Promise.
}

// An Asynchronous function return a Promise
async function openFile(file) {
    console.log("Start opening file ..."); // ***

    var willFileOpen = new Promise(
       (resolve, reject) => {
             var message = "File " + file.fileName + " opened!"
             resolve(message);
        }
    );

    return willFileOpen; // Return a Promise.
}

// Main Function (Asynchronous function)
async function mainFunction()  {
    try {
        console.log("Start app.."); // ***

        // Call downloadFile(..) function with 'await' keyword:
        // It returns a File (Not Promise)
        var file = await downloadFile("http://example.com/file.mp3");

        console.log(file);

        // Call openFile(..) function with 'await' keyword:
        // It returns a String (Not Promise)
        var message = await openFile(file);

        console.log(message);

        console.log("End app.."); // ***
    } catch(e)  {
       console.log(e.message);
    }
}

// Call Main Function:
(async () => {
    await mainFunction();
})();

 

Xem thêm các chuyên mục: