--------------- <> -----------------
--- KHOA HỌC - CÔNG NGHỆ - GIÁO DỤC - VIỆC LÀM ---
--- Học để đi cùng bà con trên thế giới ---

Tìm kiếm trong Blog

Nhập môn lập trình (2) - Chương trình dịch

Bài trước: Nhập môn lập trình (1) - Chương trình, mã máy, hợp ngữ

-----

[Video]

1.1       Chương trình dịch

Với hợp ngữ, chúng ta sẽ sử dụng trình hợp dịch để dịch từ mã hợp ngữ sang dạng mã máy. Tương tự, với ngôn ngữ cấp cao, chúng ta cũng phải sử dụng một chương trình để dịch từ ngôn ngữ cấp cao (thường gọi là chương trình nguồn – source code program hay mã nguồn - source code) sang dạng mã máy.

Tổng quát hơn, chương trình dịch là chương trình dùng để dịch mã nguồn từ dạng này sang dạng khác; có thể dịch từ ngôn ngữ cấp cao sang mã máy, từ ngôn ngữ cấp cao sang dạng mã đối tượng, từ ngôn ngữ cấp cao này sang dạng ngôn ngữ cấp cao khác, thậm chí từ ngôn ngữ cấp thấp thành ngôn ngữ cấp cao hơn.

Để đơn giản, ở đây chỉ xét đến chương trình dịch thực hiện chuyển từ ngôn ngữ cấp cao sang dạng mã máy hoặc dạng mã đối tượng. CPU hoặc máy ảo sẽ thực thi các chương trình mã máy hoặc mã đối tượng này (xem Hình 1.5).

Như vậy, để có thể điều khiển được máy tính, cần phải chuyển đổi ngôn ngữ lập trình sang dạng mã máy, khi đó CPU sẽ thực thi mã máy để thực hiện các công việc. Để chuyển từ ngôn ngữ lập trình sang mã máy cần có quá trình dịch ngôn ngữ, gồm quá trình thông dịch hoặc quá trình biên dịch, hoặc kết hợp cả hai quá trình này (xem Hình 1‑6).


Trình thông dịch

Trình thông dịch (interpreter) là một chương trình máy tính, giúp CPU thực thi trực tiếp các lệnh được viết bằng một ngôn ngữ lập trình hay ngôn ngữ kịch bản mà không yêu cầu phải biên dịch trước thành một chương trình ngôn ngữ máy (xem Hình 1‑7).


Với người lập trình, nếu lập trình bằng ngôn ngữ kiểu thông dịch sẽ không thấy tập tin kết quả của quá trình dịch, phần mềm sẽ gồm các tập tin mã nguồn ở dạng ngôn ngữ lập trình.

Ví dụ, bạn có thể viết một chương trình đơn giản bằng ngôn ngữ JavaScript để hiểu về quá trình thông dịch.

– Mở chương trình Notepad

– Nhập vào đoạn mã nguồn sau:

 

– Lưu tập tin với phần mở rộng là html (ví dụ testThongDich.html)

<script>

    alert('hi cu Teo');

</script>

– Mở tập tin vừa tạo bằng trình duyệt, sẽ thấy xuất hiện một cửa sổ với nội dung “hi cu Teo”.

– Trong trường hợp này, tạm hiểu Trình duyệt web chính là một Trình thông dịch, nó thực thi trực tiếp các dòng mã trong tập tin mã nguồn, và xuất ra kết quả. Bạn sẽ không thể tìm thấy tập tin thực thi dạng .EXE hoặc DLL.

Trình biên dịch

Trình biên dịch (compiler) là một chương trình máy tính, thực hiện dịch một chuỗi các câu lệnh của một ngôn ngữ lập trình (gọi là ngôn ngữ nguồn, mã nguồn) thành một chương trình tương đương nhưng ở dạng một ngôn ngữ máy tính mới (gọi là ngôn ngữ đích) và thường là ngôn ngữ cấp thấp hơn (dạng mã đối tượng, hoặc dạng mã máy).

CPU hoặc máy ảo sẽ thực thi mã đối tượng hoặc mã máy do trình biên dịch tạo ra.

Cũng có loại trình biên dịch chuyển đổi từ ngôn ngữ lập trình cấp thấp sang ngôn ngữ lập trình cấp cao gọi là bộ biên dịch ngược; cũng có trình biên dịch chuyển đổi từ ngôn ngữ cấp cao này sang ngôn ngữ cấp cao khác, hoặc sang một ngôn ngữ trung gian để xử lý tiếp về sau gọi là bộ biên dịch phân tầng (xem Hình 1‑8).


Với người lập trình, nếu lập trình bằng ngôn ngữ kiểu biên dịch sẽ thấy tập tin kết quả của quá trình dịch, phần mềm sẽ gồm các tập tin mã nguồn ở dạng ngôn ngữ lập trình, các tập tin mã trung gian, hoặc tập tin dạng mã máy.

Bạn sẽ hiểu rõ hơn về quá trình biên dịch khi lập trình bằng ngôn ngữ C, C++ hoặc C#.

Thông dịch hay biên dịch?

Bạn có thể chia ngôn ngữ lập trình thành các nhóm, như ngôn ngữ lập trình kiểu thông dịch, hay ngôn ngữ lập trình kiểu biên dịch. Tuy nhiên, việc phân chia này không thực sự rõ ràng, vì thực tế nhiều ngôn ngữ lập trình được thiết kế để chạy theo kiểu vừa thông dịch vừa biên dịch, tùy thuộc vào giai đoạn khác nhau của quá trình chuyển đổi và thực thi mã nguồn. Các ngôn ngữ lập trình hiện đại cũng được triển khai trên các nền tảng cho phép tùy chọn cả hai hình thức thông dịch và biên dịch.

Như vậy việc xác định một ngôn ngữ thuộc kiểu thông dịch hay biên dịch cũng không quá quan trọng. Điều quan trọng là bạn cần phải hiểu rõ các bước, các thành phần đã tham gia vào quá trình chuyển đổi từ ngôn ngữ lập trình sang dạng mã máy.

-----//2

[Video]

--------

Cập nhật: 4/7/2022

Bài tiếp theo: Nhập môn lập trình (3) - Ngôn ngữ và phần mềm lập trình


Web nâng cao (10) - JavaScript cho React (9) - Fetch_Async_Await

Bài trước: Web nâng cao (9) - JavaScript cho React (8) - Promise

-----

1.1.1       Fetch API căn bản

Như đã đề cập ở các phần trước, để trao đổi thông tin giữa trình duyệt và server, bạn đã được làm quen với đối tượng XMLHttpRequest của JavaScript. Có một cách khác để trình duyệt có thể trao đổi thông tin với server là sử dụng Fetch API.

Fetch có nghĩa là chạy đến và mang về một cái gì đó, do vậy, có thể tạm hiểu trình duyệt sẽ dùng fetch để kết nối đến server và mang thông tin về. API là viết tắt của Application Programming Interface, là công cụ (phương thức, hàm) dùng để lập trình giao tiếp giữa các phần mềm, thiết bị với nhau.

Fetch API là một thành phần của JavaScript, được sử dụng để trao đổi thông tin giữa client-server. Bạn có thể sử dụng Fetch API để tạo HTTP request, nhằm trao đổi dữ liệu giữa trình duyệt web và server.

Fetch API hoạt động dưới hình thức một promise; cho phép trao đổi thông tin theo kiểu bất đồng bộ. Với Fetch API, việc lập trình giao tiếp giữa trình duyệt và server được thực hiện đơn giản, rõ ràng và linh hoạt hơn so với XMLHttpRequest. Hầu hết các trình duyệt đều đã hỗ trợ Fetch API.

Fetch là một phương thức toàn cục của JavaScript, do vậy bạn có thể kiểm tra bằng đoạn mã sau:

console.log('fetch' in window);

    // true

Fetch hoạt động dưới dạng một promise, nên nó sẽ tồn tại ở một trong 3 trạng thái: trạng thái chờ (pending), trạng thái thực hiện thành công, và trạng thái thực hiện thất bại.

Do fetch là phương thức có sẵn của JavaScript nên bạn không cần quan tâm tới phần định nghĩa promise của fetch, mà chỉ quan tâm tới quá trình gọi fetch để thực thi và xử lý kết quả do fetch trả về.

Để tạo một request, bạn sử dụng cú pháp sau:

fetch(url);

Trong đó, url chính là địa chỉ của server; trạng thái của fetch khi mới được tạo ra sẽ ở trạng thái “chờ”. Ví dụ:

console.log(fetch("https://jsonplaceholder.typicode.com/todos/1"));

    // pending

Nếu promise thực hiện thành công (resolved), khi đó hàm callback trong then() sẽ được thực thi:

fetch(url)

    .then( (response) => {

      // đoạn mã xử lý cho trường hợp resolve được gọi

    })

Nếu promise thực hiện thất bại (rejected), khi đó hàm callback trong catch() sẽ được thực thi:

fetch(url)

    .then( (response) => {

      // đoạn mã xử lý cho trường hợp fetch thành công (resolve)

    })

    .catch( (error) => {

      // đoạn mã xử lý cho trường hợp fetch thất bại

    });

Ví dụ, sử dụng fetch để lấy thông tin người dùng trên mạng:

 fetch("https://jsonplaceholder.typicode.com/todos/1")

    .then( (response) => {

      console.log("lay duoc du lieu", response);

    })

    .catch( (error) => {

      console.log("bi loi", error);

    });

Bạn có thể sửa URL thành một đường dẫn không tồn tại (ví dụ : https://jsonplaceholder.typicode.com/todosabc/1), để fetch trả về lỗi? Nghĩa là phương thức reject sẽ được thực thi? Tuy nhiên, kết quả không phải vậy. Phương thức resolve vẫn được gọi và hàm callback trong catch không được thực thi. Tại sao có hiện tượng này?

Nếu bạn mở đối tượng response trong cửa sổ console của trình duyệt, bạn sẽ thấy trường status có giá trị là 404, nghĩa là không tìm thấy tài nguyên. Vậy, phương thức reject được thực thi khi nào? Thực tế nó chỉ được gọi khi mạng bị lỗi hoặc trình duyệt không thể gọi API vì một lý do nào đó. Bạn thử ngắt kết nối mạng và chạy lại đoạn mã để có được trạng thái reject của fetch.

Do fetch là một đối tượng, nên ngoài các phương thức, nó còn có các thuộc tính đi kèm, như response.ok, response.status; dựa vào giá trị của các thuộc tính này, bạn có thêm thông tin trước khi thực hiện các xử lý tiếp theo. Ví dụ, thuộc tính response.ok sẽ có giá trị là true nếu mã trạng thái của server trả về (status) là 200-299, ngược lại là false; response.status sẽ trả về mã trạng thái do server trả về. Bạn thử với  các URL khác nhau (đúng và sai) để xem kết quả trả về của response.status và response.ok trong đoạn mã sau:

   ...

    fetch("https://jsonplaceholder.typicode.com/todos/1")

    .then( (response) => {

      console.log(response.status);

      console.log(response.ok);

    })

   ...

Bạn nên kiểm tra response.ok là true thì mới thực hiện các xử lý với dữ liệu từ server trả về:

...

    fetch("https://jsonplaceholder.typicode.com/todosa/1")

    .then( (response) => {

      if(!response.ok) {

        console.log("Khong co du lieu", response.status);

      }

      // doan ma xu ly du lieu

    })

...

Dữ liệu server trả về sẽ ở dạng luồng dữ liệu (stream), nên bạn có thể chuyển nó sang dạng JSON để xử lý. Lưu ý: phương thức response.json() không trả về dữ liệu dạng JSON, mà nó trả về một promise mới, phương thức resolve của promise này sẽ trả về dữ liệu dạng JSON. Ví dụ:

 fetch("https://jsonplaceholder.typicode.com/todos/1")

    .then( (response) => {

      if(!response.ok) {

        console.log("Khong co du lieu", response.status);

      }

      return response.json();

    })

    .then ( (dataJSON) => {

      console.log(dataJSON);

    })

    .catch( (error) => {

      console.log("bi loi", error);

    });

1.1.2       Lập trình bất đồng bộ bằng async/await

Phiên bản ES6 đã hỗ trợ kĩ thuật lập trình bất đồng bộ bằng promise. Với ES7, bạn có thêm một kĩ thuật lập trình bất đồng bộ nữa là async/await. Ưu điểm của async/await so với promise là viết mã đơn giản, dễ đọc, dễ kiểm tra lỗi.

Với promise, để viết một hàm nghiệp vụ chạy theo kiểu bất đồng bộ, bạn phải nhúng promise vào trong hàm nghiệp vụ đó, ví dụ:

const layDuLieu = () => {

        return new Promise( (resolve, reject) => {

 

        });

      };

Với async/await, cách làm cũng tương tự, dùng từ khóa async (viết tắt của asynchronous – bất đồng bộ) để khai báo một hàm sẽ chạy theo kiểu bất đồng bộ, ví dụ:

const layDuLieu = async () => {

 

    };

Vậy là hàm layDuLieu() sẽ chạy theo chế độ bất đồng bộ, và khi thực thi, hàm layDuLieu() sẽ trả về một promise.

    const layDuLieu = async () => {

 

    };

 

    const test = layDuLieu();

    console.log(test);

    // Promise {<fulfilled>: undefined}

Giả sử trong hàm layDuLieu(), chúng ta sử dụng fetch để lấy dữ liệu từ server về client, chúng ta có thể viết như sau:

    const layDuLieu = async () => {

      fetch("https://jsonplaceholder.typicode.com/todos/1")

        .then( (response) => {

          return response.json();

        })

        .then ( (dataJSON) => {

          console.log(dataJSON);

        })

    };

    layDuLieu();

// {userId: 1, id: 1, title: 'delectus aut autem', completed: false}

Ở đoạn mã trên, phương thức fetch() lại là một promise, do vậy JavaScript sẽ tách fetch() ra khỏi luồng chính của chương trình, đợi cho tới khi fetch() thực thi xong, nó sẽ gọi các hàm callback trong phương thức then(). Với từ khóa await (nghĩa là “chờ đợi”) đặt phía trước fetch() sẽ thay thế vai trò của các phương thức then(). Đoạn mã trên sẽ được viết lại như sau:

const layDuLieu = async () => {

      const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");

      const dataJSON = await response.json();

 

      return dataJSON;

    };

    layDuLieu().then( (data) => {

      console.log(data);

    });

    // {userId: 1, id: 1, title: 'delectus aut autem', completed: false}

Các phương thức nằm sau từ khóa await sẽ trả về một promise. Nên khi thực thi hàm layDuLieu(), bạn sẽ sử dụng các phương thức then() và catch để xử lý cho các trường hợp promise thực hiện thành công hoặc thất bại.

Ví dụ cho trường hợp promise trả về thất bại, bạn thử chạy đoạn mã sau đây sau khi đã ngắt kết nối mạng để xem kết quả.

const layDuLieu = async () => {

      const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");

      const dataJSON = await response.json();

 

      return dataJSON;

    };

    layDuLieu()

    .then( (data) => {

      console.log(data);

    })

    .catch( (error) => {

      console.log("loi: ", error.message);

    });

    // loi:  Failed to fetch

Bạn hoàn toàn có thể cho promise trả về thất bại (rejected) khi có lỗi trong quá trình xử lý. Ví dụ, mặc định với status là 404, fetch() vẫn trả về response, tuy nhiên, thực tế là không lấy được dữ liệu. Trong trường hợp này, bạn sẽ sử dụng từ khóa throw để chủ động trả về thất bại. Ví dụ:

const layDuLieu = async () => {

      const response = await fetch("https://jsonplaceholder.typicode.com/todosâbc/1");

      if (response.status !== 200) {

        throw new Error('Khong fetch duoc du lieu!');

      }

      const dataJSON = await response.json();

 

      return dataJSON;

    };

    layDuLieu()

    .then( (data) => {

      console.log(data);

    })

    .catch( (error) => {

      console.log("loi: ", error.message);

    });

    // loi:  Khong fetch duoc du lieu!

Một vài nhận xét:

– Kĩ thuật async/await hoạt động trên nền tảng promise

– Từ khóa async đặt trước một hàm sẽ làm cho hàm đó thực thi theo cơ chế bất đồng bộ

– Từ khóa await chỉ được sử dụng trong các hàm có từ khóa async

– Với async/await, cách viết đoạn chương trình chạy bất đồng bộ nhưng trông có vẻ giống như lập trình đồng bộ

– Từ khóa await đứng trước một promise (một hàm), nghĩa là chương trình sẽ chờ cho promise đó được xử lý xong và trả về giá trị, trước khi thực thi các lệnh kế tiếp

1.1.3       Xem và đọc thêm

– Dùng các từ khóa sau để tìm kiếm trên mạng và đọc thêm: lập trình đồng bộ, lập trình bất đồng bộ, synchronous, asynchronous, promise, fetch API, async/await.

– Synchronous and Asynchronous: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing

– Asynchronous videos: https://www.youtube.com/watch?v=ZcQyJ-gxke0&list=PL4cUxeGkcC9jx2TTZk3IGWKSbtugYdrlu&index=1

– XMLHttpRequest.readyState: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState

– HTTP response status codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

– Promise: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise?retiredLocale=vi

– async/await: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

– [1] Alex Banks, Eve Porcello, Learning React – Mordern Patterns for Developing React Apps, O’Reilly Media, 2020, p23 – p

– [2]  Darren Jones, JavaScript Novice To Ninja, Sitepoint, 2017, p149 – p160

1.1.4       Bài tập và thực hành

Bài tập 1. Viết lại các đoạn mã trong phần lý thuyết để chạy và kiểm tra lại kết quả.

Bài tập 2. Fetch và REST

https://www.youtube.com/watch?v=TRjVXmk8q8I

1.1.5       Câu hỏi ôn tập

Câu 1. Các kĩ thuật lập trình bất đồng bộ trong JavaScript gồm?

A. callback, promise, callback hell

B. promise, callback, fetch

C. async/await, callback, promise

D. fetch, async/await, callback

Câu 2. Trong JavaScript, callback được hiểu là?

A. Kĩ thuật khai báo biến

B. Xuất một chuỗi theo mẫu có sẵn

C. Truyền hàm cho hàm khác dưới dạng một đối số.

D. Kĩ thuật nối chuỗi

Câu 3. Trong JavaScript, khi sử dụng đối tượng XMLHttpRequest để giao tiếp giữa client và server, các trạng thái của gói request tính từ lúc được tạo ra tới khi thực hiện xong pha truyền dữ liệu, theo thứ tự sẽ là:

A. UNSENT > OPENED > HEADER_RECEIVED > LOADING > DONE

B. UNSENT > LOADING > HEADER_RECEIVED > OPENED > DONE

C. LOADING > UNSENT > HEADER_RECEIVED > OPENED > DONE

D. UNSENT > OPENED > LOADING > HEADER_RECEIVED > DONE

Đáp án: 1 (C), 2 (C), 3 (A)

-----

Cập nhật: 5/5/2022

-----

Bài tiếp: Web nâng cao (11) - JavaScript cho React (10)