Thứ năm, 10/09/2020 | 00:00 GMT+7

Hiểu Vòng lặp sự kiện, Gọi lại, Hứa hẹn và Không đồng bộ / Chờ đợi trong JavaScript

Trong những ngày đầu của Internet, các trang web thường bao gồm dữ liệu tĩnh trong một trang HTML . Nhưng giờ đây, các ứng dụng web đã trở nên tương tác và năng động hơn, ngày càng cần thiết phải thực hiện các hoạt động chuyên sâu như thực hiện các yêu cầu mạng bên ngoài để truy xuất dữ liệu API . Để xử lý các hoạt động này trong JavaScript, một nhà phát triển phải sử dụng các kỹ thuật lập trình không đồng bộ .

Vì JavaScript là ngôn ngữ lập trình đơn stream với mô hình thực thi đồng bộ xử lý hết hoạt động này đến hoạt động khác, nên nó chỉ có thể xử lý một câu lệnh tại một thời điểm. Tuy nhiên, một hành động như yêu cầu dữ liệu từ API có thể mất một khoảng thời gian không xác định, tùy thuộc vào kích thước dữ liệu được yêu cầu, tốc độ kết nối mạng và các yếu tố khác. Nếu các lệnh gọi API được thực hiện theo cách đồng bộ, trình duyệt sẽ không thể xử lý bất kỳ thông tin đầu vào nào của user , chẳng hạn như cuộn hoặc nhấp vào nút, cho đến khi hoạt động đó hoàn tất. Điều này được gọi là chặn .

Để ngăn chặn hành vi chặn, môi trường trình duyệt có nhiều API Web mà JavaScript có thể truy cập không đồng bộ , nghĩa là chúng có thể chạy song song với các hoạt động khác thay vì tuần tự. Điều này rất hữu ích vì nó cho phép user tiếp tục sử dụng trình duyệt bình thường trong khi các hoạt động không đồng bộ đang được xử lý.

Là một nhà phát triển JavaScript, bạn cần biết cách làm việc với các API Web không đồng bộ và xử lý phản hồi hoặc lỗi của các hoạt động đó. Trong bài viết này, bạn sẽ tìm hiểu về vòng lặp sự kiện, cách xử lý ban đầu với hành vi không đồng bộ thông qua các lệnh gọi lại, bổ sung các hứa hẹn trong ECMAScript 2015 được cập nhật và cách sử dụng async/await hiện đại.

Lưu ý: Bài viết này tập trung vào JavaScript phía client trong môi trường trình duyệt. Các khái niệm tương tự thường đúng trong môi trường Node.js , tuy nhiên Node.js sử dụng các API C ++ của riêng nó thay vì các API Web của trình duyệt. Để biết thêm thông tin về lập trình không đồng bộ trong Node.js, hãy xem Cách viết mã không đồng bộ trong Node.js.

Vòng lặp sự kiện

Phần này sẽ giải thích cách JavaScript xử lý mã không đồng bộ với vòng lặp sự kiện. Đầu tiên nó sẽ chạy qua phần trình diễn của vòng lặp sự kiện tại nơi làm việc, và sau đó sẽ giải thích hai phần tử của vòng lặp sự kiện: ngăn xếp và hàng đợi.

Mã JavaScript không sử dụng bất kỳ API Web không đồng bộ nào sẽ thực thi theo cách đồng bộ — tuần tự từng cái một. Điều này được chứng minh bằng mã ví dụ này gọi ba hàm mà mỗi hàm in một số vào control panel :

// Define three example functions function first() {   console.log(1) }  function second() {   console.log(2) }  function third() {   console.log(3) } 

Trong đoạn mã này, bạn xác định ba hàm in số bằng console.log() .

Tiếp theo, viết lời gọi đến các hàm:

// Execute the functions first() second() third() 

Kết quả kết quả sẽ dựa trên thứ tự các hàm được gọi— first() , second() , rồi third() :

Output
1 2 3

Khi một API Web không đồng bộ được sử dụng, các luật trở nên phức tạp hơn. Một API tích hợp mà bạn có thể kiểm tra là setTimeout , đặt bộ đếm thời gian và thực hiện một hành động sau một khoảng thời gian được chỉ định. setTimeout cần phải không đồng bộ, nếu không toàn bộ trình duyệt sẽ vẫn bị đóng băng trong thời gian chờ, dẫn đến trải nghiệm user kém.

Thêm setTimeout vào hàm second để mô phỏng một yêu cầu không đồng bộ:

// Define three example functions, but one of them contains asynchronous code function first() {   console.log(1) }  function second() {   setTimeout(() => {     console.log(2)   }, 0) }  function third() {   console.log(3) } 

setTimeout nhận hai đối số: hàm mà nó sẽ chạy không đồng bộ và khoảng thời gian nó sẽ đợi trước khi gọi hàm đó. Trong đoạn mã này, bạn đã gói console.log trong một hàm ẩn danh và chuyển nó đến setTimeout , sau đó đặt hàm chạy sau 0 mili giây.

Bây giờ hãy gọi các hàm, như bạn đã làm trước đây:

// Execute the functions first() second() third() 

Bạn có thể mong đợi với setTimeout được đặt thành 0 rằng việc chạy ba hàm này sẽ vẫn dẫn đến các số được in theo thứ tự tuần tự. Nhưng vì nó không đồng bộ, hàm có thời gian chờ sẽ được in sau cùng:

Output
1 3 2

Cho dù bạn đặt thời gian chờ thành 0 giây hay năm phút sẽ không có gì khác biệt — console.log được gọi bằng mã không đồng bộ sẽ thực thi sau các chức năng cấp cao nhất đồng bộ. Điều này xảy ra do môi trường server JavaScript, trong trường hợp này là trình duyệt, sử dụng một khái niệm gọi là vòng lặp sự kiện để xử lý các sự kiện đồng thời hoặc song song. Vì JavaScript chỉ có thể thực thi một câu lệnh tại một thời điểm, nó cần vòng lặp sự kiện được thông báo về thời điểm thực thi câu lệnh cụ thể nào. Vòng lặp sự kiện xử lý điều này với các khái niệm về ngăn xếphàng đợi .

Cây rơm

Ngăn xếp , hoặc ngăn xếp cuộc gọi, giữ trạng thái của chức năng hiện đang chạy. Nếu bạn không quen với khái niệm ngăn xếp, bạn có thể tưởng tượng nó như một mảng có thuộc tính “Vào cuối cùng, xuất trước” (LIFO), nghĩa là bạn chỉ có thể thêm hoặc xóa các mục từ cuối ngăn xếp. JavaScript sẽ chạy khung hiện tại (hoặc lệnh gọi hàm trong một môi trường cụ thể) trong ngăn xếp, sau đó xóa nó và chuyển sang khung tiếp theo.

Đối với ví dụ chỉ chứa mã đồng bộ, trình duyệt xử lý việc thực thi theo thứ tự sau:

  • Thêm first() vào ngăn xếp, chạy first() ghi 1 vào console , xóa first() khỏi ngăn xếp.
  • Thêm second() vào ngăn xếp, chạy second() ghi 2 vào console , xóa second() khỏi ngăn xếp.
  • Thêm third() vào ngăn xếp, chạy third() ghi 3 vào console , xóa third() khỏi ngăn xếp.

Ví dụ thứ hai với setTimout trông như thế này:

  • Thêm first() vào ngăn xếp, chạy first() ghi 1 vào console , xóa first() khỏi ngăn xếp.
  • Thêm second() vào ngăn xếp, chạy second() .
    • Thêm setTimeout() vào ngăn xếp, chạy API web setTimeout() khởi động bộ đếm thời gian và thêm hàm ẩn danh vào hàng đợi , xóa setTimeout() khỏi ngăn xếp.
  • Xóa second() khỏi ngăn xếp.
  • Thêm third() vào ngăn xếp, chạy third() ghi 3 vào console , xóa third() khỏi ngăn xếp.
  • Vòng lặp sự kiện kiểm tra hàng đợi cho bất kỳ thư nào đang chờ xử lý và tìm hàm ẩn danh từ setTimeout() , thêm hàm vào ngăn xếp ghi log 2 vào console , sau đó xóa nó khỏi ngăn xếp.

Sử dụng setTimeout , một API Web không đồng bộ, giới thiệu khái niệm về hàng đợi , mà hướng dẫn này sẽ trình bày tiếp theo.

Xếp hàng

Hàng đợi , còn gọi là hàng đợi thông báo hoặc hàng đợi tác vụ, là một vùng chờ cho các chức năng. Khi nào ngăn xếp cuộc gọi trống, vòng lặp sự kiện sẽ kiểm tra hàng đợi xem có tin nhắn chờ nào không, bắt đầu từ tin nhắn cũ nhất. Khi nó tìm thấy một, nó sẽ thêm nó vào ngăn xếp, nó sẽ thực thi chức năng trong tin nhắn.

Trong ví dụ setTimeout , hàm ẩn danh chạy ngay sau phần còn lại của quá trình thực thi cấp cao nhất, vì bộ đếm thời gian được đặt thành 0 giây. Điều quan trọng cần nhớ là bộ đếm thời gian không nghĩa là mã sẽ thực thi chính xác trong 0 giây hoặc dù thời gian được chỉ định nào, mà nó sẽ thêm chức năng ẩn danh vào hàng đợi trong repository ảng thời gian đó. Hệ thống hàng đợi này tồn tại bởi vì nếu bộ đếm thời gian thêm chức năng ẩn danh trực tiếp vào ngăn xếp khi bộ đếm thời gian kết thúc, nó sẽ làm gián đoạn bất kỳ chức năng nào hiện đang chạy, điều này có thể gây ra các tác động không mong muốn và không thể đoán trước.

Lưu ý: Ngoài ra còn có một hàng đợi khác được gọi là hàng đợi công việc hoặc hàng đợi vi nhiệm vụ xử lý các lời hứa. Các vi nhiệm vụ như lời hứa được xử lý ở mức độ ưu tiên cao hơn so với các vi nhiệm vụ như setTimeout .

Đến đây bạn đã biết cách vòng lặp sự kiện sử dụng ngăn xếp và hàng đợi để xử lý thứ tự thực thi của mã. Nhiệm vụ tiếp theo là tìm ra cách kiểm soát thứ tự thực thi trong mã của bạn. Để làm điều này, trước tiên bạn sẽ tìm hiểu về cách ban đầu đảm bảo mã không đồng bộ được xử lý chính xác bởi vòng lặp sự kiện: các hàm gọi lại.

Chức năng gọi lại

Trong ví dụ setTimeout , hàm có thời gian chờ chạy sau mọi thứ trong ngữ cảnh thực thi cấp cao nhất chính. Nhưng nếu bạn cần đảm bảo một trong các hàm, như hàm third , chạy sau thời gian chờ, thì bạn sẽ phải sử dụng các phương pháp mã hóa không đồng bộ. Thời gian chờ ở đây có thể đại diện cho một lệnh gọi API không đồng bộ có chứa dữ liệu. Bạn muốn làm việc với dữ liệu từ lệnh gọi API, nhưng bạn phải đảm bảo dữ liệu được trả về trước.

Giải pháp ban đầu để giải quyết vấn đề này là sử dụng các hàm gọi lại . Các hàm gọi lại không có cú pháp đặc biệt; chúng chỉ là một hàm đã được truyền như một đối số cho một hàm khác. Hàm nhận một hàm khác làm đối số được gọi là hàm bậc cao hơn . Theo định nghĩa này, bất kỳ hàm nào cũng có thể trở thành hàm gọi lại nếu nó được truyền dưới dạng đối số. Bản chất các lệnh gọi lại không phải là không đồng bộ, nhưng được dùng cho các mục đích không đồng bộ.

Đây là một ví dụ về mã cú pháp của một hàm bậc cao hơn và một lệnh gọi lại:

// A function function fn() {   console.log('Just a function') }  // A function that takes another function as an argument function higherOrderFunction(callback) {   // When you call a function that is passed as an argument, it is referred to as a callback   callback() }  // Passing a function higherOrderFunction(fn) 

Trong đoạn mã này, bạn xác định một hàm fn , xác định một hàm higherOrderFunction nhận một hàm callback làm đối số và chuyển fn làm một lệnh gọi lại cho higherOrderFunction .

Chạy mã này sẽ cung cấp những điều sau:

Output
Just a function

Hãy quay lại các hàm first , secondthird với setTimeout . Đây là những gì bạn có cho đến nay:

function first() {   console.log(1) }  function second() {   setTimeout(() => {     console.log(2)   }, 0) }  function third() {   console.log(3) } 

Nhiệm vụ là làm cho hàm third luôn trì hoãn thực thi cho đến khi hành động không đồng bộ trong hàm second đã hoàn thành. Đây là nơi các lệnh gọi lại xuất hiện. Thay vì thực hiện first , secondthird ở cấp độ thực thi cao nhất, bạn sẽ chuyển hàm third làm đối số cho hàm second . Hàm second sẽ thực hiện lệnh gọi lại sau khi hành động không đồng bộ đã hoàn thành.

Dưới đây là ba hàm có áp dụng lệnh gọi lại:

// Define three functions function first() {   console.log(1) }  function second(callback) {   setTimeout(() => {     console.log(2)      // Execute the callback function     callback()   }, 0) }  function third() {   console.log(3) } 

Bây giờ, thực thi firstsecond , sau đó chuyển third làm đối số cho second :

first() second(third) 

Sau khi chạy khối mã này, bạn sẽ nhận được kết quả sau:

Output
1 2 3

Đầu tiên 1 sẽ in và sau khi bộ đếm thời gian hoàn thành (trong trường hợp này là 0 giây, nhưng bạn có thể thay đổi nó thành bất kỳ số lượng nào), nó sẽ in 2 rồi 3 . Bằng cách chuyển một hàm dưới dạng gọi lại, bạn đã trì hoãn thực thi thành công hàm cho đến khi API Web không đồng bộ ( setTimeout ) hoàn tất.

Điểm mấu chốt ở đây là các hàm gọi lại không phải là không đồng setTimeout là Web API không đồng bộ chịu trách nhiệm xử lý các việc không đồng bộ. Lệnh gọi lại chỉ cho phép bạn được thông báo về thời điểm một tác vụ không đồng bộ đã hoàn thành và xử lý thành công hay thất bại của tác vụ.

Đến đây bạn đã học cách sử dụng các lệnh gọi lại để xử lý các việc không đồng bộ, phần tiếp theo giải thích các vấn đề của việc lồng quá nhiều lệnh gọi lại và tạo ra một “kim tự tháp của sự diệt vong”.

Gọi lại lồng nhau và Kim tự tháp diệt vong

Các hàm gọi lại là một cách hiệu quả đảm bảo việc thực thi một hàm bị trì hoãn cho đến khi một hàm khác hoàn thành và trả về cùng với dữ liệu. Tuy nhiên, do tính chất lồng ghép của các lệnh gọi lại, mã có thể trở nên lộn xộn nếu bạn có nhiều yêu cầu không đồng bộ liên tiếp dựa vào nhau. Đây là một sự thất vọng lớn đối với các nhà phát triển JavaScript ngay từ đầu và kết quả là mã chứa các lệnh gọi lại lồng nhau thường được gọi là “kim tự tháp của sự diệt vong” hoặc “địa ngục gọi lại”.

Dưới đây là minh họa về các lệnh gọi lại lồng nhau:

function pyramidOfDoom() {   setTimeout(() => {     console.log(1)     setTimeout(() => {       console.log(2)       setTimeout(() => {         console.log(3)       }, 500)     }, 2000)   }, 1000) } 

Trong mã này, mỗi setTimeout mới được lồng vào bên trong một hàm bậc cao hơn, tạo ra một hình kim tự tháp gồm các lệnh gọi lại sâu và rộng hơn. Chạy mã này sẽ cho kết quả sau:

Output
1 2 3

Trong thực tế, với mã không đồng bộ trong thế giới thực, điều này có thể phức tạp hơn nhiều. Rất có thể bạn cần thực hiện xử lý lỗi trong mã không đồng bộ, sau đó chuyển một số dữ liệu từ mỗi phản hồi vào yêu cầu tiếp theo. Làm điều này với lệnh gọi lại sẽ khiến mã của bạn khó đọc và khó bảo trì.

Dưới đây là một ví dụ có thể chạy được về “kim tự tháp diệt vong” thực tế hơn mà bạn có thể sử dụng:

// Example asynchronous function function asynchronousRequest(args, callback) {   // Throw an error if no arguments are passed   if (!args) {     return callback(new Error('Whoa! Something went wrong.'))   } else {     return setTimeout(       // Just adding in a random number so it seems like the contrived asynchronous function       // returned different data       () => callback(null, {body: args + ' ' + Math.floor(Math.random() * 10)}),       500,     )   } }  // Nested asynchronous requests function callbackHell() {   asynchronousRequest('First', function first(error, response) {     if (error) {       console.log(error)       return     }     console.log(response.body)     asynchronousRequest('Second', function second(error, response) {       if (error) {         console.log(error)         return       }       console.log(response.body)       asynchronousRequest(null, function third(error, response) {         if (error) {           console.log(error)           return         }         console.log(response.body)       })     })   }) }  // Execute  callbackHell() 

Trong đoạn mã này, bạn phải tính toán mọi hàm cho một response có thể xảy ra và một error có thể error , làm cho hàm callbackHell trở nên khó hiểu.

Chạy mã này sẽ cung cấp cho bạn những điều sau:

Output
First 9 Second 3 Error: Whoa! Something went wrong. at asynchronousRequest (<anonymous>:4:21) at second (<anonymous>:29:7) at <anonymous>:9:13

Cách xử lý mã không đồng bộ này rất khó làm theo. Kết quả là, khái niệm về lời hứa đã được giới thiệu trong ES6. Đây là trọng tâm của phần tiếp theo.

Lời hứa

Một lời hứa đại diện cho việc hoàn thành một chức năng không đồng bộ. Nó là một đối tượng có thể trả về một giá trị trong tương lai. Nó hoàn thành mục tiêu cơ bản giống như một hàm gọi lại, nhưng với nhiều tính năng bổ sung và cú pháp dễ đọc hơn. Là một nhà phát triển JavaScript, bạn có thể sẽ dành nhiều thời gian hơn cho các lời hứa so với việc tạo chúng, vì nó thường là các API Web không đồng bộ trả lại một lời hứa để nhà phát triển sử dụng. Hướng dẫn này sẽ chỉ cho bạn cách thực hiện cả hai.

Tạo một lời hứa

Bạn có thể khởi tạo một lời hứa bằng cú pháp new Promise và bạn phải khởi tạo nó bằng một hàm. Hàm được chuyển đến một lời hứa có các tham số resolvereject . Các chức năng resolvereject xử lý sự thành công và thất bại của một hoạt động, tương ứng.

Viết dòng sau để tuyên bố một lời hứa:

// Initialize a promise const promise = new Promise((resolve, reject) => {}) 

Nếu bạn kiểm tra lời hứa đã khởi tạo ở trạng thái này bằng console của trình duyệt web, bạn sẽ thấy nó có trạng thái pending và giá trị undefined :

Output
__proto__: Promise [[PromiseStatus]]: "pending" [[PromiseValue]]: undefined

Lúc này, không có gì được cài đặt cho lời hứa, vì vậy nó sẽ nằm ở đó trong trạng thái pending mãi mãi. Điều đầu tiên bạn có thể làm để kiểm tra một lời hứa là thực hiện lời hứa bằng cách giải quyết nó với một giá trị:

const promise = new Promise((resolve, reject) => {   resolve('We did it!') }) 

Bây giờ, khi kiểm tra lời hứa, bạn sẽ thấy rằng nó có trạng thái fulfilled và một value được đặt thành giá trị bạn đã chuyển để resolve :

Output
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: "We did it!"

Như đã nói ở đầu phần này, một lời hứa là một đối tượng có thể trả về một giá trị. Sau khi được thực hiện thành công, value chuyển từ undefined thành được điền vào dữ liệu.

Một lời hứa có thể có ba trạng thái: đang chờ xử lý, được thực hiện và bị từ chối.

  • Đang chờ xử lý - Trạng thái ban đầu trước khi được giải quyết hoặc bị từ chối
  • Hoàn thành - hoạt động thành công, lời hứa đã được giải quyết
  • Bị từ chối - Thao tác không thành công, lời hứa đã bị từ chối

Sau khi được thực hiện hoặc bị từ chối, một lời hứa được giải quyết.

Đến đây bạn đã có ý tưởng về cách các lời hứa được tạo ra, hãy xem cách một nhà phát triển có thể sử dụng những lời hứa này.

Thực hiện một lời hứa

Lời hứa trong phần cuối cùng đã hoàn thành với một giá trị, nhưng bạn cũng muốn có thể truy cập giá trị. Các lời hứa có một phương thức được gọi then đó sẽ chạy sau khi một lời hứa đạt được resolve trong mã. then sẽ trả về giá trị của lời hứa như một tham số.

Đây là cách bạn sẽ trả về và ghi lại value của lời hứa mẫu:

promise.then((response) => {   console.log(response) }) 

Lời hứa bạn đã tạo đã có [[PromiseValue]] của We did it! . Giá trị này là giá trị sẽ được chuyển vào hàm ẩn danh dưới dạng response :

Output
We did it!

Lúc này, ví dụ bạn đã tạo không liên quan đến API Web không đồng bộ — nó chỉ giải thích cách tạo, phân giải và sử dụng một hứa hẹn JavaScript root . Sử dụng setTimeout , bạn có thể kiểm tra một yêu cầu không đồng bộ.

Đoạn mã sau mô phỏng dữ liệu được trả về từ một yêu cầu không đồng bộ dưới dạng một lời hứa:

const promise = new Promise((resolve, reject) => {   setTimeout(() => resolve('Resolving an asynchronous request!'), 2000) })  // Log the result promise.then((response) => {   console.log(response) }) 

Sử dụng cú pháp then đảm bảo response sẽ chỉ được ghi lại khi hoạt động setTimeout hoàn thành sau 2000 mili giây. Tất cả điều này được thực hiện mà không cần lồng các lệnh gọi lại.

Bây giờ sau hai giây, nó sẽ giải quyết giá trị lời hứa và nó sẽ được đăng nhập then :

Output
Resolving an asynchronous request!

Các hứa hẹn cũng có thể được xâu chuỗi để truyền dữ liệu đến nhiều hơn một hoạt động không đồng bộ. Nếu một giá trị được trả về trong then , khác then có thể được thêm vào để thỏa mãn với giá trị trả về trước then :

// Chain a promise promise   .then((firstResponse) => {     // Return a new value for the next then     return firstResponse + ' And chaining!'   })   .then((secondResponse) => {     console.log(secondResponse)   }) 

Phản hồi được thực hiện trong giây then sẽ ghi lại giá trị trả về:

Output
Resolving an asynchronous request! And chaining!

Kể từ then có thể được xâu chuỗi, nó cho phép sử dụng các lời hứa xuất hiện đồng bộ hơn các lệnh gọi lại, vì chúng không cần phải lồng vào nhau. Điều này sẽ cho phép mã dễ đọc hơn có thể được duy trì và xác minh dễ dàng hơn.

Xử lý lỗi

Lúc này, bạn chỉ xử lý một lời hứa với một resolve thành công, điều này đặt lời hứa ở trạng thái fulfilled . Nhưng thông thường với một yêu cầu không đồng bộ, bạn cũng phải xử lý lỗi — nếu API gặp sự cố hoặc một yêu cầu không đúng định dạng hoặc trái phép được gửi đi. Một lời hứa sẽ có thể xử lý cả hai trường hợp. Trong phần này, bạn sẽ tạo một hàm để kiểm tra cả trường hợp thành công và lỗi khi tạo và sử dụng một lời hứa.

Hàm getUsers này sẽ chuyển một cờ cho một lời hứa và trả về lời hứa:

function getUsers(onSuccess) {   return new Promise((resolve, reject) => {     setTimeout(() => {       // Handle resolve and reject in the asynchronous API     }, 1000)   }) } 

Cài đặt mã để nếu onSuccesstrue , thời gian chờ sẽ ứng với một số dữ liệu. Nếu false , hàm sẽ từ chối và báo lỗi :

function getUsers(onSuccess) {   return new Promise((resolve, reject) => {     setTimeout(() => {       // Handle resolve and reject in the asynchronous API       if (onSuccess) {         resolve([           {id: 1, name: 'Jerry'},           {id: 2, name: 'Elaine'},           {id: 3, name: 'George'},         ])       } else {         reject('Failed to fetch data!')       }     }, 1000)   }) } 

Để có kết quả thành công, bạn trả về các đối tượng JavaScript đại diện cho dữ liệu user mẫu.

Để xử lý lỗi, bạn sẽ sử dụng phương thức catch instance. Điều này sẽ cung cấp cho bạn một cuộc gọi lại thất bại với error là một tham số.

Chạy lệnh getUser với onSuccess được đặt thành false , sử dụng phương thức then cho trường hợp thành công và phương thức catch lỗi:

// Run the getUsers function with the false flag to trigger an error getUsers(false)   .then((response) => {     console.log(response)   })   .catch((error) => {     console.error(error)   }) 

Vì lỗi đã được kích hoạt, then sẽ bị bỏ qua và lệnh catch sẽ xử lý lỗi:

Output
Failed to fetch data!

Nếu bạn chuyển cờ và resolve thay vào đó, catch sẽ bị bỏ qua và dữ liệu sẽ trả về thay vào đó:

// Run the getUsers function with the true flag to resolve successfully getUsers(true)   .then((response) => {     console.log(response)   })   .catch((error) => {     console.error(error)   }) 

Điều này sẽ mang lại dữ liệu user :

Output
(3) [{…}, {…}, {…}] 0: {id: 1, name: "Jerry"} 1: {id: 2, name: "Elaine"} 3: {id: 3, name: "George"}

Để tham khảo, đây là bảng với các phương thức xử lý trên các đối tượng Promise :

phương pháp Sự miêu tả
then() Xử lý một resolve . Trả về một lời hứa và gọi hàm onFulfilled không đồng bộ
catch() Xử lý reject . Trả về một lời hứa và gọi hàm onRejected không đồng bộ
finally() Được gọi khi một lời hứa được giải quyết. Trả về một lời hứa và gọi hàm onFinally không đồng bộ

Những lời hứa có thể gây nhầm lẫn cho cả các nhà phát triển mới và các lập trình viên có kinh nghiệm chưa từng làm việc trong môi trường không đồng bộ trước đây. Tuy nhiên, như đã đề cập, việc sử dụng lời hứa phổ biến hơn nhiều so với việc tạo ra chúng. Thông thường, API Web của trình duyệt hoặc thư viện bên thứ ba sẽ cung cấp lời hứa và bạn chỉ cần sử dụng nó.

Trong phần lời hứa cuối cùng, hướng dẫn này sẽ trích dẫn một trường hợp sử dụng phổ biến của API Web trả về các lời hứa: API Tìm nạp .

Sử dụng API tìm nạp với lời hứa

Một trong những API Web hữu ích và được sử dụng thường xuyên nhất trả về một lời hứa là API Tìm nạp, cho phép bạn thực hiện một yêu cầu tài nguyên không đồng bộ qua mạng. fetch là một quá trình gồm hai phần, và do đó đòi hỏi chaining then . Ví dụ này cho thấy việc sử dụng API GitHub để tìm nạp dữ liệu của user , đồng thời xử lý mọi lỗi tiềm ẩn:

// Fetch a user from the GitHub API fetch('https://api.github.com/users/octocat')   .then((response) => {     return response.json()   })   .then((data) => {     console.log(data)   })   .catch((error) => {     console.error(error)   }) 

Yêu cầu fetch được gửi đến URL https://api.github.com/users/octocat , URL này chờ phản hồi một cách không đồng bộ. Đầu tiên, then chuyển phản hồi đến một hàm ẩn danh định dạng phản hồi dưới dạng dữ liệu JSON , sau đó chuyển JSON đến giây then ghi dữ liệu vào console . Câu lệnh catch ghi lại bất kỳ lỗi nào vào console .

Chạy mã này sẽ mang lại kết quả sau:

Output
login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

Đây là dữ liệu được yêu cầu từ https://api.github.com/users/octocat , được hiển thị ở định dạng JSON.

Phần này của hướng dẫn cho thấy rằng các hứa hẹn kết hợp rất nhiều cải tiến để xử lý mã không đồng bộ. Tuy nhiên, trong khi việc sử dụng then để xử lý các hành động không đồng bộ dễ thực hiện hơn so với kim tự tháp của các lệnh gọi lại, một số nhà phát triển vẫn thích một định dạng đồng bộ để viết mã không đồng bộ. Để giải quyết nhu cầu này, ECMAScript 2016 (ES7) giới thiệu async chức năng và await từ khóa để làm việc với những lời hứa dễ dàng hơn.

Chức năng không đồng bộ với async/await

Một async chức năng cho phép bạn xử lý mã không đồng bộ một cách xuất hiện đồng bộ. async hàm async vẫn sử dụng các hứa hẹn bên dưới, nhưng có cú pháp JavaScript truyền thống hơn. Trong phần này, bạn sẽ thử các ví dụ về cú pháp này.

Bạn có thể tạo một hàm async bằng cách thêm từ khóa async trước một hàm:

// Create an async function async function getUser() {   return {} } 

Mặc dù hàm này chưa xử lý bất kỳ thứ gì không đồng bộ, nhưng nó hoạt động khác với một hàm truyền thống. Nếu bạn thực thi hàm, bạn sẽ thấy rằng nó trả về một lời hứa với [[PromiseStatus]][[PromiseValue]] thay vì giá trị trả về.

Hãy thử điều này bằng cách đăng nhập một cuộc gọi đến hàm getUser :

console.log(getUser()) 

Điều này sẽ cung cấp những điều sau:

Output
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: Object

Điều này có nghĩa bạn có thể xử lý một async chức năng với then trong cùng một cách bạn có thể xử lý một lời hứa. Hãy thử điều này với mã sau:

getUser().then((response) => console.log(response)) 

Lệnh gọi getUser chuyển giá trị trả về cho một hàm ẩn danh ghi giá trị vào console .

Bạn sẽ nhận được những thứ sau khi chạy chương trình này:

Output
{}

Một async chức năng có thể xử lý một lời hứa gọi là bên trong nó bằng cách sử dụng await điều hành. await được dùng trong vòng một async chức năng và sẽ đợi cho đến khi một Settles lời hứa trước khi thực hiện các mã được chỉ định.

Với kiến thức này, bạn có thể viết lại yêu cầu Tìm nạp từ phần cuối cùng bằng cách sử dụng async / await như sau:

// Handle fetch with async/await async function getUser() {   const response = await fetch('https://api.github.com/users/octocat')   const data = await response.json()    console.log(data) }  // Execute async function getUser() 

Các nhà khai thác await ở đây đảm bảo data không được ghi lại trước khi yêu cầu nhập dữ liệu vào.

Bây giờ data cuối cùng có thể được xử lý bên trong hàm getUser mà không cần sử dụng then . Đây là kết quả của data ghi log :

Output
login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

Lưu ý: Trong nhiều môi trường, async là cần thiết để sử dụng await —however, một số version mới của trình duyệt và Node cho phép sử dụng await cấp cao nhất, cho phép bạn bỏ qua việc tạo một hàm async để kết hợp await .

Cuối cùng, vì bạn đang xử lý lời hứa đã hoàn thành trong hàm không đồng bộ, bạn cũng có thể xử lý lỗi trong hàm. Thay vì sử dụng phương thức catch với then , bạn sẽ sử dụng mẫu try / catch để xử lý ngoại lệ.

Thêm mã được đánh dấu sau:

// Handling success and errors with async/await async function getUser() {   try {     // Handle success in try     const response = await fetch('https://api.github.com/users/octocat')     const data = await response.json()      console.log(data)   } catch (error) {     // Handle error in catch     console.error(error)   } } 

Chương trình bây giờ sẽ bỏ qua khối catch nếu nó nhận được lỗi và ghi lỗi đó vào console .

Mã JavaScript không đồng bộ hiện đại thường được xử lý bằng cú pháp async / await , nhưng điều quan trọng là phải có kiến thức làm việc về cách hoạt động của các hứa hẹn, đặc biệt là vì các hứa hẹn có khả năng bổ sung các tính năng không thể xử lý với async / await , như kết hợp các hứa hẹn với Promise.all() .

Lưu ý: async / await có thể được tái tạo bằng cách sử dụng trình tạo kết hợp với các hứa hẹn để thêm tính linh hoạt hơn cho mã của bạn. Để tìm hiểu thêm, hãy xem hướng dẫn Hiểu về Trình tạo trong JavaScript của ta .

Kết luận

Vì các API Web thường cung cấp dữ liệu không đồng bộ nên việc học cách xử lý kết quả của các hành động không đồng bộ là một phần thiết yếu để trở thành một nhà phát triển JavaScript. Trong bài viết này, bạn đã biết cách môi trường server sử dụng vòng lặp sự kiện để xử lý thứ tự thực thi mã với ngăn xếphàng đợi . Bạn cũng đã thử các ví dụ về ba cách để xử lý sự thành công hay thất bại của một sự kiện không đồng bộ, với các lệnh gọi lại, lời hứa và cú pháp async / await . Cuối cùng, bạn đã sử dụng API Web tìm nạp để xử lý các hành động không đồng bộ.

Để biết thêm thông tin về cách trình duyệt xử lý các sự kiện song song, hãy đọc Mô hình đồng thời và vòng lặp sự kiện trên Mạng nhà phát triển Mozilla. Nếu bạn muốn tìm hiểu thêm về JavaScript, hãy quay lại loạt bài Cách viết mã trong JavaScript của ta .


Tags:

Các tin liên quan

Bốn phương pháp để tìm kiếm thông qua các mảng trong JavaScript
2020-09-09
Sử dụng phương thức Array.find trong JavaScript
2020-09-09
split () Phương thức chuỗi trong JavaScript
2020-09-09
Khám phá các hàm Async / Await trong JavaScript
2020-09-04
module ES6 và Cách sử dụng Nhập và Xuất trong JavaScript
2020-09-04
Giải thích về phương pháp rút gọn JavaScript
2020-09-04
filter () Phương thức mảng trong JavaScript
2020-09-03
Hiểu các hàm mũi tên trong JavaScript
2020-07-31
Cách tạo phần tử kéo và thả với Vanilla JavaScript và HTML
2020-07-27
Hiểu các chữ mẫu trong JavaScript
2020-06-30