HomeLập trìnhJavaScriptAsync Await JavaScript...

Async Await JavaScript Tutorial – Cách đợi một chức năng kết thúc trong JS


Khi nào một chức năng không đồng bộ kết thúc? Và tại sao đây là một câu hỏi khó trả lời?

Hóa ra là để hiểu được các hàm không đồng bộ đòi hỏi rất nhiều kiến ​​thức về cách thức hoạt động cơ bản của JavaScript.

Hãy cùng khám phá khái niệm này và tìm hiểu nhiều điều về JavaScript trong quá trình này.

Bạn đã sẵn sàng chưa? Đi nào.

Mã không đồng bộ là gì?

Theo thiết kế, JavaScript là ngôn ngữ lập trình đồng bộ. Điều này có nghĩa là khi mã được thực thi, JavaScript sẽ bắt đầu ở đầu tệp và chạy qua từng dòng mã cho đến khi hoàn thành.

Kết quả của quyết định thiết kế này là chỉ có một điều có thể xảy ra tại một thời điểm.

Bạn có thể nghĩ về điều này như thể bạn đang tung hứng sáu quả bóng nhỏ. Trong khi bạn đang tung hứng, tay của bạn đang bận và không thể xử lý bất cứ việc gì khác.

Điều này cũng tương tự với JavaScript: một khi mã đang chạy, nó sẽ chứa đầy mã đó. Chúng tôi gọi đây là loại mã đồng bộ chặn. Bởi vì nó chặn mã khác chạy một cách hiệu quả.

Hãy quay lại ví dụ tung hứng. Điều gì sẽ xảy ra nếu bạn muốn thêm một quả bóng khác? Thay vì sáu quả bóng, bạn muốn tung hứng bảy quả bóng. Đó có thể là một vấn đề.

Bạn không muốn ngừng tung hứng, bởi vì nó rất thú vị. Nhưng bạn cũng không thể đi lấy một quả bóng khác, vì điều đó có nghĩa là bạn phải dừng lại.

Giải pháp? Ủy thác công việc cho một người bạn hoặc thành viên gia đình. Họ không tung hứng, vì vậy họ có thể đi lấy bóng cho bạn, sau đó tung nó vào phần tung hứng của bạn vào thời điểm bạn rảnh tay và bạn đã sẵn sàng để thêm một quả bóng khác vào giữa màn tung hứng.

Đây là mã không đồng bộ. JavaScript đang ủy thác công việc cho một thứ khác, sau đó tiến hành công việc kinh doanh của chính nó. Sau đó, khi nó sẵn sàng, nó sẽ nhận lại kết quả từ công việc.

Ai đang làm công việc kia?

Được rồi, vậy là chúng ta biết rằng JavaScript đồng bộ và lười biếng. Nó không muốn tự mình làm tất cả công việc, vì vậy nó chuyển nó sang thứ khác.

Nhưng ai là thực thể bí ẩn làm việc cho JavaScript này? Và làm thế nào để nó được thuê để làm việc cho JavaScript?

Chà, hãy xem một ví dụ về mã không đồng bộ.

const logName = () => {
   console.log("Han")
}

setTimeout(logName, 0)

console.log("Hi there")

Chạy mã này dẫn đến đầu ra sau trong bảng điều khiển:

// in console
Hi there
Han

Ổn thỏa. Điều gì đang xảy ra?

Hóa ra cách chúng tôi phát triển công việc trong JavaScript là sử dụng các hàm và API dành riêng cho môi trường. Và đây là một nguồn gây nhầm lẫn lớn trong JavaScript.

JavaScript luôn chạy trong một môi trường.

Thông thường, môi trường đó là trình duyệt. Nhưng nó cũng có thể ở trên máy chủ với NodeJS. Nhưng những gì trên trái đất là sự khác biệt?

Sự khác biệt – và điều này rất quan trọng – là trình duyệt và máy chủ (NodeJS), về mặt chức năng, không tương đương nhau. Chúng thường giống nhau, nhưng chúng không giống nhau.

Hãy minh họa điều này với một ví dụ. Giả sử JavaScript là nhân vật chính của một cuốn sách giả tưởng hoành tráng. Chỉ là một đứa trẻ nông dân bình thường.

Bây giờ hãy nói rằng đứa trẻ nông dân này đã tìm thấy hai bộ áo giáp đặc biệt mang lại cho chúng sức mạnh vượt xa sức mạnh của chính chúng.

Khi họ sử dụng bộ áo giáp trình duyệt, họ có quyền truy cập vào một số khả năng nhất định.

Khi họ sử dụng bộ áo giáp của máy chủ, họ có quyền truy cập vào một bộ khả năng khác.

Những bộ quần áo này có một số trùng lặp, bởi vì những người tạo ra những bộ quần áo này có cùng nhu cầu ở một số nơi nhất định, nhưng không phải ở những nơi khác.

Đây là những gì một môi trường là. Nơi mã được chạy, nơi tồn tại các công cụ được xây dựng dựa trên ngôn ngữ JavaScript hiện có. Chúng không phải là một phần của ngôn ngữ, nhưng dòng này thường bị mờ vì chúng tôi sử dụng các công cụ này hàng ngày khi chúng tôi viết mã.

Đọc thêm  Cách đảo ngược một mảng trong JavaScript – Hàm JS .reverse()

setTimeout, tìm nạp và DOM đều là các ví dụ về API Web. (Bạn có thể xem danh sách đầy đủ các API Web tại đây.) Chúng là những công cụ được tích hợp trong trình duyệt và được cung cấp cho chúng tôi khi mã của chúng tôi được chạy.

Và bởi vì chúng tôi luôn chạy JavaScript trong một môi trường, có vẻ như đây là một phần của ngôn ngữ. Nhưng họ không phải vậy.

Vì vậy, nếu bạn đã từng thắc mắc tại sao bạn có thể sử dụng tìm nạp trong JavaScript khi chạy nó trong trình duyệt (nhưng cần cài đặt gói khi bạn chạy nó trong NodeJS), thì đây là lý do tại sao. Ai đó nghĩ rằng tìm nạp là một ý tưởng hay và xây dựng nó như một công cụ cho môi trường NodeJS.

Gây nhầm lẫn? Đúng!

Nhưng giờ đây, cuối cùng chúng ta cũng có thể hiểu những gì đảm nhận công việc từ JavaScript và cách nó được tuyển dụng.

Hóa ra chính môi trường đảm nhận công việc và cách để môi trường thực hiện công việc đó là sử dụng chức năng thuộc về môi trường. Ví dụ tìm về hoặc setTimeout trong môi trường trình duyệt.

Điều gì xảy ra với công việc?

Tuyệt quá. Vì vậy, môi trường đảm nhận công việc. Rồi sao?

Tại một số điểm bạn cần lấy lại kết quả. Nhưng hãy nghĩ xem nó sẽ hoạt động như thế nào.

Hãy quay lại ví dụ tung hứng từ đầu. Hãy tưởng tượng bạn yêu cầu một quả bóng mới và một người bạn bắt đầu ném quả bóng vào bạn khi bạn chưa sẵn sàng.

Đó sẽ là một thảm họa. Có thể bạn sẽ gặp may mắn và nắm bắt được nó và đưa nó vào thói quen của mình một cách hiệu quả. Nhưng có khả năng lớn là nó có thể khiến bạn đánh rơi tất cả các quả bóng của mình và làm hỏng thói quen của bạn. Sẽ không tốt hơn nếu bạn đưa ra những hướng dẫn nghiêm ngặt về thời điểm nhận bóng?

Hóa ra, có những quy tắc nghiêm ngặt xung quanh thời điểm JavaScript có thể nhận công việc được ủy quyền.

Các quy tắc đó được điều chỉnh bởi vòng lặp sự kiện và liên quan đến hàng đợi vi tác vụ và tác vụ vĩ mô. Vâng tôi biết. Nó rất nhiều. Nhưng chịu đựng với tôi.

autodraw-31_08_2020

Ổn thỏa. Vì vậy, khi chúng tôi ủy quyền mã không đồng bộ cho trình duyệt, trình duyệt sẽ nhận và chạy mã cũng như đảm nhận khối lượng công việc đó. Nhưng có thể có nhiều tác vụ được giao cho trình duyệt, vì vậy chúng tôi cần đảm bảo rằng chúng tôi có thể ưu tiên các tác vụ này.

Đây là nơi hàng đợi vi tác vụ và hàng đợi tác vụ vĩ mô phát huy tác dụng. Trình duyệt sẽ nhận công việc, thực hiện nó, sau đó đặt kết quả vào một trong hai hàng đợi dựa trên loại công việc mà nó nhận được.

Ví dụ: các lời hứa được đặt trong hàng đợi vi tác vụ và có mức độ ưu tiên cao hơn.

Sự kiện và setTimeout là những ví dụ về công việc được đặt trong hàng đợi tác vụ vĩ mô và có mức độ ưu tiên thấp hơn.

Bây giờ, khi công việc đã hoàn thành và được đặt vào một trong hai hàng đợi, vòng lặp sự kiện sẽ chạy đi chạy lại và kiểm tra xem JavaScript đã sẵn sàng nhận kết quả hay chưa.

Chỉ khi JavaScript chạy xong tất cả mã đồng bộ của nó, đồng thời tốt và sẵn sàng, vòng lặp sự kiện mới bắt đầu chọn từ hàng đợi và chuyển các chức năng trở lại JavaScript để chạy.

Vì vậy, hãy xem một ví dụ:

setTimeout(() => console.log("hello"), 0) 

fetch("https://someapi/data").then(response => response.json())
                             .then(data => console.log(data))

console.log("What soup?")

Thứ tự sẽ là gì đây?

  1. Đầu tiên, setTimeout được ủy quyền cho trình duyệt, trình duyệt này sẽ thực hiện công việc và đưa hàm kết quả vào hàng đợi tác vụ macro.
  2. Lần tìm nạp thứ hai được ủy quyền cho trình duyệt, trình duyệt này sẽ thực hiện công việc. Nó truy xuất dữ liệu từ điểm cuối và đặt các hàm kết quả vào hàng đợi vi tác vụ.
  3. Javascript đăng xuất “Súp gì”?
  4. Vòng lặp sự kiện kiểm tra xem JavaScript đã sẵn sàng nhận kết quả từ công việc được xếp hàng đợi hay chưa.
  5. Khi console.log hoàn tất, JavaScript đã sẵn sàng. Vòng lặp sự kiện chọn các chức năng đã xếp hàng đợi từ hàng đợi vi tác vụ, có mức độ ưu tiên cao hơn và đưa chúng trở lại JavaScript để thực thi.
  6. Sau khi hàng đợi vi tác vụ trống, lệnh gọi lại setTimeout được đưa ra khỏi hàng đợi tác vụ lớn và được trả lại cho JavaScript để thực thi.
In console:
// What soup?
// the data from the api
// hello

lời hứa

Bây giờ bạn đã có nhiều kiến ​​thức về cách mã không đồng bộ được xử lý bởi JavaScript và môi trường trình duyệt. Vì vậy, hãy nói về những lời hứa.

Đọc thêm  Cách sử dụng Bộ sưu tập JavaScript – Bản đồ và Tập hợp

Lời hứa là một cấu trúc JavaScript đại diện cho một giá trị không xác định trong tương lai. Về mặt khái niệm, một lời hứa chỉ là JavaScript hứa hẹn sẽ trở lại một giá trị. Đó có thể là kết quả của lệnh gọi API hoặc có thể là đối tượng lỗi do yêu cầu mạng không thành công. Bạn được đảm bảo để có được một cái gì đó.

const promise = new Promise((resolve, reject) => {
	// Make a network request
   if (response.status === 200) {
      resolve(response.body)
   } else {
      const error = { ... }
      reject(error)
   }
})

promise.then(res => {
	console.log(res)
}).catch(err => {
	console.log(err)
})

Một lời hứa có thể có các trạng thái sau:

  • hoàn thành – hành động hoàn thành thành công
  • bị từ chối – hành động không thành công
  • đang chờ xử lý – chưa có hành động nào được hoàn thành
  • giải quyết – đã được thực hiện hoặc bị từ chối

Một lời hứa nhận được một chức năng giải quyết và từ chối có thể được gọi để kích hoạt một trong những trạng thái này.

Một trong những điểm hấp dẫn nhất của lời hứa là chúng ta có thể xâu chuỗi các chức năng mà chúng ta muốn xảy ra khi thành công (giải quyết) hoặc thất bại (từ chối):

  • Để đăng ký một chức năng để chạy thành công, chúng tôi sử dụng .then
  • Để đăng ký một chức năng để chạy khi thất bại, chúng tôi sử dụng .catch
// Fetch returns a promise
fetch("https://swapi.dev/api/people/1")
	.then((res) => console.log("This function is run when the request succeeds", res)
    .catch(err => console.log("This function is run when the request fails", err)
           
// Chaining multiple functions
 fetch("https://swapi.dev/api/people/1")
	.then((res) => doSomethingWithResult(res))
    .then((finalResult) => console.log(finalResult))
    .catch((err => doSomethingWithErr(err))

Hoàn hảo. Bây giờ chúng ta hãy xem xét kỹ hơn điều này trông như thế nào dưới mui xe, sử dụng tìm nạp làm ví dụ:

const fetch = (url, options) => {
  // simplified
  return new Promise((resolve, reject) => {

  const xhr = new XMLHttpRequest()
  // ... make request
  xhr.onload = () => {
    const options = {
        status: xhr.status,
        statusText: xhr.statusText
        ...
    }
    
    resolve(new Response(xhr.response, options))
  }
  
  xhr.onerror = () => {
    reject(new TypeError("Request failed"))
  }
}
 
 fetch("https://swapi.dev/api/people/1")
   // Register handleResponse to run when promise resolves
	.then(handleResponse)
  .catch(handleError)
  
 // conceptually, the promise looks like this now:
 // { status: "pending", onsuccess: [handleResponse], onfailure: [handleError] }
  
 const handleResponse = (response) => {
  // handleResponse will automatically receive the response, ¨
  // because the promise resolves with a value and automatically injects into the function
   console.log(response)
 }
 
  const handleError = (response) => {
  // handleError will automatically receive the error, ¨
  // because the promise resolves with a value and automatically injects into the function
   console.log(response)
 }
  
// the promise will either resolve or reject causing it to run all of the registered functions in the respective arrays
// injecting the value. Let's inspect the happy path:
  
// 1. XHR event listener fires
// 2. If the request was successfull, the onload event listener triggers
// 3. The onload fires the resolve(VALUE) function with given value
// 4. Resolve triggers and schedules the functions registered with .then
  
  

Vì vậy, chúng tôi có thể sử dụng các lời hứa để thực hiện công việc không đồng bộ và để đảm bảo rằng chúng tôi có thể xử lý bất kỳ kết quả nào từ những lời hứa đó. Đó là đề xuất giá trị. Nếu bạn muốn biết thêm về những lời hứa, bạn có thể đọc thêm về chúng tại đây và tại đây.

Khi chúng tôi sử dụng lời hứa, chúng tôi xâu chuỗi các chức năng của mình vào lời hứa để xử lý các tình huống khác nhau.

Điều này hoạt động, nhưng chúng tôi vẫn cần xử lý logic bên trong các cuộc gọi lại (các hàm lồng nhau) sau khi chúng tôi nhận lại kết quả của mình. Điều gì sẽ xảy ra nếu chúng ta có thể sử dụng lời hứa nhưng viết mã tìm kiếm đồng bộ? Hóa ra chúng ta có thể.

Đọc thêm  Định dạng ngày JavaScript – Cách định dạng ngày trong JS

Không đồng bộ/Đang chờ

Async/Await là một cách viết lời hứa cho phép chúng ta viết mã không đồng bộ theo cách đồng bộ. Chúng ta hãy có một cái nhìn.

const getData = async () => {
    const response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
    const data = await response.json()
    
    console.log(data)
}

getData()

Không có gì thay đổi dưới mui xe ở đây. Chúng tôi vẫn đang sử dụng các lời hứa để tìm nạp dữ liệu, nhưng giờ đây nó có vẻ đồng bộ và chúng tôi không còn các khối .then và .catch nữa.

Async/Await thực chất chỉ là đường cú pháp cung cấp một cách để tạo mã dễ suy luận hơn mà không thay đổi động lực cơ bản.

Chúng ta hãy xem nó hoạt động như thế nào.

Async/Await cho phép chúng tôi sử dụng trình tạo để tạm ngừng việc thực hiện một chức năng. Khi chúng tôi đang sử dụng async/await, chúng tôi không chặn vì chức năng này đang mang lại quyền kiểm soát cho chương trình chính.

Sau đó, khi lời hứa được giải quyết, chúng tôi đang sử dụng trình tạo để mang lại quyền kiểm soát cho hàm không đồng bộ với giá trị từ lời hứa đã giải quyết.

Bạn có thể đọc thêm tại đây để có cái nhìn tổng quan tuyệt vời về trình tạo và mã không đồng bộ.

Trên thực tế, bây giờ chúng ta có thể viết mã không đồng bộ trông giống như mã đồng bộ. Điều đó có nghĩa là sẽ dễ suy luận hơn và chúng ta có thể sử dụng các công cụ đồng bộ để xử lý lỗi, chẳng hạn như try/catch:

const getData = async () => {
    try {
    	const response = await fetch("https://jsonplaceholder.typicode.com/todos/1")
    	const data = await response.json()
        console.log(data)
    } catch (err) {
       console.log(err)
    }
    
}

getData()

Ổn thỏa. Vì vậy, làm thế nào để chúng ta sử dụng nó? Để sử dụng async/await, chúng ta cần thêm chức năng async vào trước. Điều này không làm cho nó trở thành một chức năng không đồng bộ, nó chỉ cho phép chúng ta sử dụng sự chờ đợi bên trong nó.

Việc không cung cấp từ khóa async sẽ dẫn đến lỗi cú pháp khi cố gắng sử dụng chức năng chờ bên trong một chức năng thông thường.

const getData = async () => {
	console.log("We can use await in this function")
}

Do đó, chúng tôi không thể sử dụng async/await trên mã cấp cao nhất. Nhưng không đồng bộ và chờ đợi vẫn chỉ là đường cú pháp hơn là lời hứa. Vì vậy, chúng tôi có thể xử lý các trường hợp cấp cao nhất với chuỗi lời hứa:

async function getData() {
  let response = await fetch('http://apiurl.com');
}

// getData is a promise
getData().then(res => console.log(res)).catch(err => console.log(err); 

Điều này phơi bày một sự thật thú vị khác về async/await. Khi xác định một chức năng là không đồng bộ, nó sẽ luôn trả lại một lời hứa.

Lúc đầu, việc sử dụng async/await có vẻ giống như phép thuật. Nhưng giống như bất kỳ phép thuật nào, nó chỉ là công nghệ đủ tiên tiến đã phát triển qua nhiều năm. Hy vọng rằng bây giờ bạn đã nắm vững các nguyên tắc cơ bản và có thể tự tin sử dụng async/await.

Nếu bạn đã thực hiện nó ở đây, xin chúc mừng. Bạn vừa thêm một phần kiến ​​thức quan trọng về JavaScript và cách nó hoạt động với các môi trường của nó vào hộp công cụ của mình.

Đây chắc chắn là một chủ đề khó hiểu và các dòng không phải lúc nào cũng rõ ràng. Nhưng bây giờ, hy vọng bạn đã nắm được cách JavaScript hoạt động với mã không đồng bộ trong trình duyệt và hiểu rõ hơn về cả lời hứa và không đồng bộ / chờ đợi.

Nếu bạn thích bài viết này, bạn cũng có thể thích kênh youtube của tôi. Tôi hiện đang có một loạt bài về kiến ​​thức cơ bản về web liên quan đến HTTP, xây dựng máy chủ web từ đầu và hơn thế nữa.

Ngoài ra còn có một loạt bài về xây dựng toàn bộ ứng dụng bằng React, nếu đó là vấn đề của bạn. Và tôi dự định sẽ thêm nhiều nội dung hơn nữa ở đây trong tương lai, đi sâu vào các chủ đề JavaScript.

Và nếu bạn muốn nói xin chào hoặc trò chuyện về phát triển web, bạn luôn có thể liên hệ với tôi trên twitter tại @foseberg. Cảm ơn vì đã đọc!



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status