🔏
PexpA
  • Home
  • Knowledge
    • Design Pattern
    • RxJS
    • Computer Graphics
      • Phép biến đổi hình học
    • Javascript
      • Generator function và Yield trong javascript
      • Asynchronous và Synchronous
    • GraphQL
      • Core Concepts
      • Xây dựng GraphQL sử dụng NodeJS
    • Analysis and System Design
    • SEO
    • Database
      • NoSQL
        • MongoDB
      • SQL
    • ReactJS
      • React Fragment
      • Lifecycle trong component
      • HOCs
      • What is Ref ?
      • Context API
      • React Hooks
        • useState Hook
        • useEffect Hook
        • useLayoutEffect Hook
        • Khi nào dùng useLayoutEffect và useEffect
        • useContext Hook
        • useReducer Hook
        • useCallback Hook
        • useMemo Hook
        • useRef Hook
        • Building Your Own Hooks
      • Redux
    • React Native
      • Animations
    • Angular
    • Python
      • Object Oriented Programming
      • Decorator
      • Multi Threading
      • Generators
      • Iterators
    • Java
    • Blockchain
      • Ethereum Development Overview
      • Solidity Document
      • JSON RPC Protocol
  • Package
    • React Router V4
    • API Documentation
      • API Blueprint
      • Swagger
    • Lazyload image
    • React Helmet
    • React Spring
    • React Apollo
    • ImmerJS
    • Styled components
  • Experience
    • Sử dụng Latex trên VSCode
    • Linked List in C++
    • How to using Date, Time, TimeStamp for Java to connect Database
    • Pass props to a component rendered by React Router v4
    • Forking Workflow
  • Deploy
    • Heroku
      • How to deploy React App with Express
      • How to deploy React App with Python
      • How to deploy React App with Java
  • About me
Powered by GitBook
On this page
  • Javascript hoạt động đồng bộ như thế nào ?
  • Javascript hoạt động bất đồng bộ như thế nào ?
  • Các phương pháp xử lý bất đồng bộ

Was this helpful?

  1. Knowledge
  2. Javascript

Asynchronous và Synchronous

Bất đồng bộ và đồng bộ trong Javascript

  • Javascript là ngôn ngữ đơn luồng (single-threaded), điều này có nghĩa là nó chỉ có thể xử lý một câu lệnh tại một thời điểm.

  • Single-threaded language đơn giản hoá việc viết code, khi bạn không cần phải quan tâm đến concurrency, tuy nhiên khi bạn thực hiện những tác vụ tiêu tốn thời gian dài để hoàn thành (chẳng hạn như network access) thì luồng chính (main thread) sẽ bị block.

Xử lý đồng thời - Concurrency - là việc nhiều task được xử lý cùng một lúc. (Có thể hiểu như là một xử lý bất đồng bộ - Asynchronous)

Hãy thử tưởng tượng bạn truy cập 1 trang web và nó request đến 1 API và API đó tiêu tốn 2 phút để phản hồi, nếu với cơ chế Single threaded thì chắc hẳn chúng ta phải chờ 2 phút thì các đoạn lệnh khác mới được thực hiện sao ?

Javascript không ngu ngốc như vậy, nó có 1 thứ cực kì hay ho mà chúng ta gọi là cơ chế bất đồng bộ (asynchronous)

Javascript hoạt động đồng bộ như thế nào ?

Trước khi tìm hiểu về cơ chế bất đồng bộ - Asynchronous ta sẽ tìm hiểu về cơ chế đồng bộ Synchronous

const second = () => {
  console.log('Hello there!');
}
const first = () => {
  console.log('Hi there!');
  second();
  console.log('The End');
}
first();

Để hiểu được đoạn code trên được thực thi như thế nào bên trong Javascript Engine, chúng ta cần hiểu những khái niệm về execution context và call stack (execution stack)

Execution Context

Execution context là môi trường, nơi JavaScript code được thực thi. Function code được thực thi bên trong function execution context, global code được thực thi bên trong global execution context. Mỗi function code được thực thi bên trong function execution context của riêng nó.

Call Stack

Call stack là một stack với cơ chế LIFO (last in first out), được dùng để lưu trữ tất cả execution context được tạo ra trong suốt quá trình thực thi code.

Và dĩ nhiên Javascript chỉ có 1 call stack vì nó là single-threaded language. Đồng nghĩa với việc item chỉ có thể được thêm vào hoặc lấy ra từ top của stack.

Đọạn code phía trên sẽ được JavaScript engine thực thi như minh hoạ dưới đây

Việc thực thi đoạn code trên được giải thích như sau:

  • Đầu tiên thì global execution context sẽ được tạo ra (đại diện bởi main()) và được đẩy vào top của call stack.

  • Tiếp đến nó thực thi đến lời gọi hàm first(), thì function execution context được tạo khi thực thi hàm first() sẽ được đẩy tiếp vào top của call stack.

  • kế tiếp console.log(‘Hi there!’) được push vào top của call stack, sau khi nó được thực thi xong, nó được lấy ra khỏi call stack. Khi Javascript engine thực thi lời gọi hàm second() nó push function execution context của second() lên top của call stack.

  • console.log(‘Hello there!’) được push vào top của call stack và được lấy ra khỏi call stack khi nó finish, hàm second() finish và nó được lấy ra khỏi call stack.

  • Chương trình finish và main() được lấy ra khỏi call stack.

Javascript hoạt động bất đồng bộ như thế nào ?

Khi bạn nghe nói Javascript là một ngôn ngữ bất đồng bộ, nó có nghĩa là bạn có thể điều khiển để chương trình hoạt động như là bất đồng bộ nhưng thực ra về bản chất, các hoạt động vẫn diễn ra tuần tự chứ không phải đồng thời. Nghe có vẻ khó hiểu nhưng đó là vì các ngôn ngữ lập trình như C/C++, Java,… để thực thi bất đồng bộ sẽ sử dụng cơ chế đa luồng tức là có nhiều luồng thực thi đồng thời, mỗi luồng thực hiện các công việc khác nhau. Nhưng Javascript lại có nền tảng là đơn luồng, nên không thể thực thi đồng thời, bất đồng bộ theo cách trên. Vậy nên để xử lý bất đồng bộ, Javascript đã phát triển một sô cách đó là: Callback, Promise và Async/await.

Blocking và Non-Blocking là gì ?

Blocking và Non-Blocking trong lập trình chủ yếu được đề cập khi muốn miêu tả về cách một chương trình thực hiện các dòng lệnh của nó. Chúng ta có thể hiểu một cách đơn giản, nếu chương trình được thực hiện theo mô hình Blocking có nghĩa là các dòng lệnh được thực hiện một cách tuần tự. Khi một dòng lệnh ở phía trước chưa được hoàn thành thì các dòng lệnh phía sau sẽ chưa được thực hiện và phải đợi khi mà thao tác phía trước hoàn tất, và nếu như các dòng lệnh trước là các thao tác cần nhiều thời gian xử lý như liên quan đến IO (input/output) hay mạng (networking) thì bản thân nó sẽ trở thành vật cản trở ( blocker ) cho các lệnh xử lý phía sau mặc dù theo logic thì có những việc ở phía sau ta có thể xử lý được luôn mà không cần phải đợi vì chúng không có liên quan gì đến nhau.

Mô hình blocking tồn tại từ lịch sử, khi mà máy tính chỉ có thể xử lý đơn nhiệm trên một lõi (core) của bộ vi xử lý (chip). Nhưng theo thời gian, công nghệ ngày một trưởng thành với những thành tựu về phần cứng, máy tính giờ có thể làm nhiều việc cùng một lúc thì người ta cần phải suy nghĩ đến việc làm sao tận dụng được tối đa tài nguyên xử lý của máy tính và tránh lãng phí nó. Từ đó mà bất cứ chỗ nào có phần xử lý Blocking không cần thiết, người ta cần thay vào một giải pháp xử dụng tài nguyên khôn ngoan hơn, đó là Non-Blocking.

Trong mô hình Non-Blocking, các dòng lệnh không nhất thiết phải lúc nào cũng phải thực hiện một cách tuần tự (sequential) và đồng bộ (synchronous) với nhau. Ở mô hình này nếu như về mặt logic dòng lệnh phía sau không phụ thuộc vào kết quả của dòng lệnh phía trước, thì nó cũng có thể hoàn toàn được thực hiện ngay sau khi dòng lệnh phía trước được gọi mà không cần đợi cho tới khi kết quả được sinh ra. Những dòng lệnh phía trước miêu tả ở trên còn có thể gọi là được thực hiện theo cách không đồng bộ (Asynchronous), và đi theo mỗi dòng lệnh thường có một callback (lời gọi lại) là đoạn mã sẽ được thực hiện ngay sau khi có kết quả trả về từ dòng lệnh không đồng bộ. Để thực hiện mô hình Non-Blocking, người ta có những cách để thực hiện khác nhau, nhưng về cơ bản vẫn dựa vào việc dùng nhiều Thread (luồng) khác nhau trong cùng một Process (tiến trình), hay thậm chí nhiều Process khác nhau (inter-process communication – IPC) để thực hiện. Và mẫu thiết kết (design pattern) tên là event-loop là một trong những mẫu thiết kế nổi tiếng để thực hiện cơ chế Non-Blocking

The Event Loop

Nhiệm vu của Event Loop sẽ quan sát Call Stack để xác định xem Call Stack hiện có đang empty hay không. Nếu Call Stack empty thì sẽ quan sát Message Queue để xem hiện có callback nào đang chờ để được thực thi hay không

Các phương pháp xử lý bất đồng bộ

Sử dụng callback để xử lý bất đồng bộ

Khi hành động bắt đầu, rồi khi nó kết thúc, hàm callback sẽ được gọi ngay sau đó.

function doAsync(url, onSuccess, onError) {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", url);
  xhr.onload = () => onSuccess(xhr.responseText);
  xhr.onerror = () => onError(xhr.statusText);
  xhr.send();
}
// Usage:
doAsync(
  "https://something.com",
  value => {
    // 'value' is corresponding with 'xhr.responseText'
  },
  error => {
    // 'error' is corresponding with 'xhr.statusText'
  }
);

Ở đây, hàm doAsync là một hàm bất đồng bộ với 2 hàm callback là: onSuccess và onError. Khi request trên thành công thì hàm onSuccess sẽ được gọi, ngược lại hàm onError sẽ được gọi.

doAsync là một hàm bất đồng bộ vì nó chứa hàm callback

Sử dụng Promise để xử lý bất đồng bộ

Cú pháp cơ bản của Promise

let promise = new Promise(function(resolve, reject) {
  // Code here
});

Trong đó, hàm được truyền vào new Promise gọi là executor.

Ban đầu, Promise có state là pending và kết quả value là undefined. Khi executor kết thúc công việc, nó sẽ gọi đến 1 trong 2 hàm được truyền vào:

  • resolve(value): để xác định rằng công việc đã thực hiện thành công

    • state chuyển thành fulfilled

    • kết quả là value

  • reject(error): để xác định rằng đã có lỗi xảy ra

    • state chuyển thành rejected

    • kết quả là error

Khi sử dụng Promise, ví dụ phía trên sẽ trở thành:

function doAsync(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

// Usage:
doAsync("https://something.com")
  .then(value => {
    // 'value' is corresponding with 'xhr.responseText'
  })
  .catch(error => {
    // 'error' is corresponding with 'xhr.statusText'
  });

Sử dụng Async/Await để xử lý bất đồng bộ

Async/await là một cú pháp đặc biệt giúp bạn làm việc với Promise dễ dàng hơn. Khi sử dụng async/await, cấu trúc chương trình xử lý bất đồng bộ sẽ giống với chương trình xử lý đồng bộ hơn.

function doAsync(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

// Usage:
async function run() {
  let responseText1, responseText2;

  try {
    responseText1 = await doAsync("https://something.com");
    responseText2 = await doAsync("https://other.com");
  } catch (error) {
    /*
     * 'error' is corresponding with 'xhr.statusText'
     * from either 'https://something.com' or 'https://other.com'
     */
  }
}

run();

PreviousGenerator function và Yield trong javascriptNextGraphQL

Last updated 4 years ago

Was this helpful?

Sử dụng Callback có thể nói là cách đầu tiên và dễ nhất giúp bạn xử lý bất đồng bộ. Khi định nghĩa một thực hiện một nhiệm vụ tốn thời gian, bạn cần truyền thêm tham số vào hàm - đóng vai trò là hàm callback.

Ví dụ dưới đây sẽ thực hiện một GET . Thông thường, việc này sẽ tốn thời gian

function
request