Xử lý bất đồng bộ trong Javascript

JavaScript là 1 ngôn ngữ đồng bộ(synchronous) theo mặc định và là đơn luồng(single threaded). Điều này có nghĩa là ta không thể tạo một luồng mới và nó sẽ chỉ thực thi khối mã của bạn theo thứ tự trước sau.

Các ngôn ngữ lập trình như C, Java, C #, PHP, Go, Ruby, Swift và Python đều là ngôn ngữ đồng bộ theo mặc định, một số ngôn ngữ có thể xử lý bất đồng bộ(asynchronous) bằng cách sử dụng các luồng và tạo ra một tiến trình mới.

Đây là 1 ví dụ về đồng bộ:

const a = 1;
console.log(a + 1);
console.log('3');
handleSomething();

Các dòng mã trên sẽ được thực thi theo chuỗi, lần lượt từng dòng một.

Vì JavaScript được sinh ra trong trình duyệt, nên công việc chính của nó ban đầu là phản hồi các hành động của người dùng, như onClick, onMouseOver, onChange, onSubmit, v.v. Làm thế nào nó có thể làm điều này với một mô hình lập trình đồng bộ?

Trong một số trường hợp, ví dụ, khi bạn muốn tìm nạp một số dữ liệu từ máy chủ (có thể mất một khoảng thời gian không xác định), thì chương trình của bạn sẽ bị đóng băng hoàn toàn trong khi nó chờ dữ liệu đó được tải xuống. Điều này thật sự tệ khi ta phải bắt người dùng phải chờ đợi trong trường hợp tải lượng dữu liệu lớn về. Vì vậy, thay vì làm điều đó, nó rất phổ biến khi chỉ chạy tác vụ tìm nạp dữ liệu trong nền.

Điều này có nghĩa là nếu bạn có hai hàm liên tiếp với hàm A không đồng bộ thì hàm B sẽ được thực thi trong khi hàm A vẫn đang chạy. Trong trường hợp này nếu hàm B phụ thuộc vào dữ liệu mà hàm A đang tìm nạp, bạn sẽ gặp vấn đề với dữ liệu của bạn. Đó chính là bất đồng bộ.

Không đồng bộ có nghĩa là mọi hành động có thể thực hiện độc lập với luồng chương trình chính.

Và để xử lý với các luồng bất động bộ, trong javascript ta có 1 số phương pháp sau:

Callbacks

Callback là một đoạn code được truyền như một tham số của một hàm(function) và chờ để được gọi vào thực thi. Với một callback bạn có thể đảm bảo rằng function B chỉ được gọi sau khi function A kết thúc bởi vì function A chịu trách nhiệm gọi function B.

Ta có ví dụ sau:

// doSomething => functionA
// callback => functionB
function doSomething (options, callback) {
    callback (options);
}
doSomething(options, callback);

Callback thực sự rất hữu ích trong các trường hợp đơn giản, tuy nhiên mỗi callback đều thêm một mức lồng nhau và khi bạn có nhiều callback, đoạn mã của bạn sẽ bắt đầu trở nên phức tạp rất nhanh.

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach(item => {
        //your code here
      })
    }, 2000)
  })
})

Đây chỉ là một đoạn mã với 4 callback lồng nhau, nhưng bạn đã thấy nhiều cấp độ lồng nhau hơn và nó thực sự không tốt.

Bắt đầu từ ES6, JavaScript đã giới thiệu một số tính năng giúp chúng ta sử dụng đoạn mã không đồng bộ không liên quan đến việc sử dụng các callback.

Promises

Đầu tiên đó là promises. Là một cách để bạn xử lý các vấn đề gặp phải với callback, ngăn chặn việc gọi quá nhiều callback trong đoạn mã của bạn. Đồng thời nó có cú pháp rõ ràng và giúp bạn xử lý lỗi 1 cách dễ dàng hơn.

Tạo một promises

Trước tiên, chúng ta tạo một ví dụ về 'promises' bằng cách gọi từ khóa new trên lớp Promise, xem mã bên dưới:

var promise = new Promise((resolve, reject) => {

});

Hãy cùng khám phá đoạn mã trên. Chúng ta truyền một hàm vào bên trong lớp Promises. ((resolve, reject) => { }) . Đây là môt arrow function với 2 đối số là resolvereject. Chúng ta cũng có thể gọi các đối số này với bất cứ tên khác, đây chỉ là tên của đối số.

Bây giờ chúng ta sẽ tìm hiểu bên trong hàm xem thực sự nó đã làm gì. Bên trong function, nó sẽ thực hiện 1 tiến trình không đồng bộ, và sau đó khi mà đã sẵn sàng nó sẽ gọi tới phương thức resolve().

Resolving a Promise

Chúng ta có thể nhận được thông báo khi một promises được giải quyết(resolves) hay chưa bằng cách đính kèm success/error vào function then khi gọi promises, xem mã dưới đây:

// #2 create an anAsyncMethod()
// this method will return an object of `Promise`
anAsyncMethod() {
    let promise = new Promise((resolve, reject) => {
        // ...
        // here code come to resolve or reject promise
        
        // call this method to resolve promise, like this:
        resolve();
        
        // call this method to reject promise, like this:
        reject();
    });
    // return of a promise is required
    return promise;
}

// #1 calling an anAsyncMethod()
// over this method to its then function attaching a success/error handler arrow-functions
anAsyncMethod().then(
  () => console.log("Task Complete! Comes here after promise resolved"),
  () => console.log("Task Errored! Comes here after promise rejected"),
);
<!DOCTYPE html>
<html>
<body>
	<script>
        // let's set default value of error to false
        let error = false;

        function asyncQueue() {
            console.log("#2 We're inside asyncQueue-function.");
            // create an instance of a 'promise'
            var promise = new Promise((resolve, reject) => {
                // let's supose it takes 2000 milliseconds
                // for my turn to come
                setTimeout(() => {
                    console.log("Async Work Complete");
                    console.log("#3 After 2000 milliseconds. My turn comes.");
                    // after 2000 milliseconds
                    // calling a `reject` or `resolve` function
                    if (error) {
                        console.log("Call the reject() function, if an error in the async task.");
                        reject();
                    } else {
                        console.log("Call the resolve() function, if an async task completed without an error.");
                        resolve();
                    }
                }, 1000);
            });
            return promise;

        }

        // suppose we are in a asynchronous queue
        // let's call an async-function to get a ticket
        console.log("#1 Call asyncQueue-function to get ticket.");
        // get notified when a promise resolves by attaching a success/error handlers to its then function
        asyncQueue().then(
            ()  => {
                console.log("Success handler called - when task complete without error!");
                console.log("#4 It's time to buy a ticket :-)");
            },
            () => {
                console.log("Error handler called - when task has an error!");
            },
        );
    </script>
</body>
</html> 

Chúng ta hãy xem output của đoạn mã trên để hiểu rõ hơn:

Async/Await

Tiếp theo chúng ta sẽ tìm hiểu về async/await. Đây là một phương pháp mới giúp ta xử lý bất đồng bộ dựa trên Promises.

Thêm một từ khóa async trước bất kỳ function nào có nghĩa là function đó sẽ luôn trả lại một promises.

await chỉ hoạt động bên trong các hàmasyncawait đợi cho đến khi promises được giải quyết và sẵn sàng trả về kết quả.

Khai báo Async functions

Async function có thể được khai báo đơn giản bằng cách thêm từ khóa async trước một function, xem mã bên dưới đây:

async function myAsyncFunc() {
  return 123;
}

Và khi gọi tới function này, ta bắt buộc phải gọi .then () qua lệnh gọi hàm để resolve , xem mã bên dưới đây:

<!DOCTYPE html>
<html>
<body>
	<script>

    	// `async` function | define a function start with `async` keyword
        async function myAsyncFunc() {
            // #1   `async` ensures that the function returns a promise,
            //      even without explicitly return
            //      return 123;

            // #2   we can also `explicitly` return a promise 
            //      this works same as above return
            return Promise.resolve(123);

            // we can do both the ways but
            // as `async` ensures that the function returns a promise
            // so why to write extra code to return explicitly
        }

		// calling a function - and to get return result call then()
		// the function inside then() will return the value 
        myAsyncFunc().then((returnVal) => {
            console.log(returnVal); // 123
        }); 

    </script>
</body>
</html> 

Chúng ta hãy xem kết quả:

Sử dụng Await

Một từ khóa khác await, chỉ hoạt động bên trong các async function. Chức năng Await được sử dụng để chờ Promises giải quyết và nó chờ cho đến khi Promises trả về kết quả. Công việc của nó là chỉ chờ khối async.

Sử dụng await với function async giống như tạo 1 function đồng bộ. Bởi vì await giữ đoạn mã thực thi và chờ Promises được giải quyết - sau đó mới cho phép dòng mã tiếp theo của bạn thực thi , xem mã bên dưới:

<!DOCTYPE html>
<html>
<body>
	<script>

        // `async` function | define a function start with `async` keyword
        async function myAsyncFunc() {
            // #1  create a Promise (learn more about creating in step #2 Promises)
            console.log('#2 within a function creating a Promise.');
            let promise = new Promise((resolve, reject) => {
                // declare a setTimeout - to hold the code for 2000 milliseconds
                console.log('#3 inside promise setTimeout to wait for 2 seconds.');
                setTimeout(() => {
                    console.log('#5 calling resolve() function after 2 seconds.');
                    // resolve a promise after 2000 milliseconds
                    resolve("resolve done! 123");
                }, 2000);
            });

            console.log('#4 I am here to add `await` keyword before a promise.');
            console.log('...waiting time... 2 seconds...');
            // the next line of code can't be executed until the promise resolves
            // because we ↓ added `await` keyword before a promise
            let promiseResult = await promise; 

            console.log('#6 I couldn`t execute until promise resolves.');
            console.log('** If I called means promise is resolved.');

            // do alert to show the world that adding `await` keyword wait for `promise` to resolve
            alert(promiseResult); // "resolve done! 123"

            console.log('#7 Finally return promiseResult.');
            return promiseResult;
        }

        // calling a function - and to get return result call then()
		// the function inside then() will return the value
        console.log('#1 call an async function.');
        myAsyncFunc().then((returnVal) => {
            // I'll be called at the end of everything
            console.log('After waiting 2 seconds.. I get the promiseResult.');
            console.log('Result:', returnVal); // 123
        }); ; 

    </script>
</body>
</html> 

Và đây là kết quả:

Như vậy là ta đã đi qua đôi chút về một số phương pháp xử lý bất đồng bộ trong Javascript.

Tài liệu tham khảo https://www.codewithchintan.com/javascript-callbacks-promises-async-await/ https://developer.mozilla.org/vi/docs/Web/JavaScript/Reference/Statements/async_function