Thứ tư, 05/02/2020 | 00:00 GMT+7

Cách viết mã không đồng bộ trong Node.js

Đối với nhiều chương trình bằng JavaScript, mã được thực thi khi nhà phát triển viết nó — từng dòng một. Điều này được gọi là thực thi đồng bộ , bởi vì các dòng được thực hiện lần lượt theo thứ tự chúng được viết. Tuy nhiên, không phải mọi hướng dẫn bạn đưa ra cho máy tính đều cần phải được thực hiện ngay lập tức. Ví dụ: nếu bạn gửi một yêu cầu mạng, quá trình thực thi mã của bạn sẽ phải đợi dữ liệu trả về trước khi có thể hoạt động trên đó. Trong trường hợp này, thời gian sẽ bị lãng phí nếu nó không thực thi mã khác trong khi chờ yêu cầu mạng được hoàn thành. Để giải quyết vấn đề này, các nhà phát triển sử dụng lập trình không đồng bộ , trong đó các dòng mã được thực thi theo thứ tự khác với thứ tự mà chúng đã được viết. Với lập trình không đồng bộ, ta có thể thực thi mã khác trong khi đợi các hoạt động lâu như yêu cầu mạng kết thúc.

Mã JavaScript được thực thi trên một stream duy nhất trong một quy trình máy tính. Mã của nó được xử lý đồng bộ trên stream này, chỉ chạy một lệnh tại một thời điểm. Do đó, nếu ta thực hiện một tác vụ lâu dài trên chuỗi này, tất cả các mã còn lại sẽ bị chặn cho đến khi tác vụ hoàn tất. Bằng cách tận dụng các tính năng lập trình không đồng bộ của JavaScript, ta có thể giảm tải các việc đang chạy dài vào một chuỗi nền để tránh vấn đề này. Khi nhiệm vụ hoàn thành, mã ta cần để xử lý dữ liệu của nhiệm vụ được đưa trở lại stream đơn chính.

Trong hướng dẫn này, bạn sẽ học cách JavaScript quản lý các việc không đồng bộ với sự trợ giúp từ Vòng lặp sự kiện , đây là một cấu trúc JavaScript hoàn thành một tác vụ mới trong khi chờ đợi một tác vụ khác. Sau đó, bạn sẽ tạo một chương trình sử dụng lập trình không đồng bộ để yêu cầu danh sách phim từ API Studio Ghibli và lưu dữ liệu vào tệp CSV . Mã không đồng bộ sẽ được viết theo ba cách: gọi lại, hứa hẹn và với từ khóa async / await .

Lưu ý: Kể từ khi viết bài này, lập trình không đồng bộ không còn được thực hiện chỉ bằng cách sử dụng các lệnh gọi lại, nhưng việc học phương pháp lỗi thời này có thể cung cấp bối cảnh tuyệt vời về lý do tại sao cộng đồng JavaScript hiện sử dụng các hứa hẹn. Các từ khóa async / await cho phép ta sử dụng các hứa hẹn theo cách ít dài dòng hơn và do đó là cách tiêu chuẩn để thực hiện lập trình không đồng bộ trong JavaScript tại thời điểm viết bài này.

Yêu cầu

Vòng lặp sự kiện

Hãy bắt đầu bằng cách nghiên cứu các hoạt động bên trong của việc thực thi hàm JavaScript. Hiểu cách hoạt động của điều này sẽ cho phép bạn viết mã không đồng bộ có chủ ý hơn và sẽ giúp bạn khắc phục sự cố mã trong tương lai.

Khi trình thông dịch JavaScript thực thi mã, mọi hàm được gọi sẽ được thêm vào ngăn xếp cuộc gọi của JavaScript. Ngăn xếp cuộc gọi là ngăn xếp — một cấu trúc dữ liệu giống như danh sách trong đó các mục chỉ có thể được thêm vào đầu và xóa khỏi đầu. Các ngăn xếp tuân theo nguyên tắc “Vào cuối cùng, xuất trước” hoặc nguyên tắc LIFO. Nếu bạn thêm hai mục trên ngăn xếp, mục được thêm mới nhất sẽ bị xóa trước.

Hãy minh họa bằng một ví dụ bằng cách sử dụng ngăn xếp cuộc gọi. Nếu JavaScript gặp một hàm functionA() đang được gọi, nó sẽ được thêm vào ngăn xếp cuộc gọi. Nếu hàm đó functionA functionA() gọi một hàm khác functionB functionB() , thì functionB() được thêm vào đầu ngăn xếp lệnh gọi. Khi JavaScript hoàn thành việc thực thi một hàm, nó sẽ bị xóa khỏi ngăn xếp cuộc gọi. Do đó, JavaScript sẽ thực thi functionB() trước tiên, xóa nó khỏi ngăn xếp khi hoàn tất, sau đó kết thúc quá trình thực thi functionA() và xóa nó khỏi ngăn xếp cuộc gọi. Đây là lý do tại sao các hàm bên trong luôn được thực thi trước các hàm bên ngoài của chúng.

Khi JavaScript gặp phải một hoạt động không đồng bộ, chẳng hạn như ghi vào một file , nó sẽ thêm nó vào một bảng trong bộ nhớ của nó. Bảng này lưu trữ thao tác, điều kiện hoàn thành và hàm sẽ được gọi khi hoàn thành. Khi hoạt động hoàn tất, JavaScript sẽ thêm chức năng được liên kết vào hàng đợi thông báo . Hàng đợi là một cấu trúc dữ liệu giống danh sách khác, nơi các mục chỉ có thể được thêm vào cuối nhưng bị xóa khỏi trên cùng. Trong hàng đợi thông báo, nếu hai hoặc nhiều hoạt động không đồng bộ đã sẵn sàng cho các chức năng của chúng được thực thi, thì hoạt động không đồng bộ được hoàn thành trước sẽ được đánh dấu chức năng của nó để thực thi trước.

Các hàm trong hàng đợi tin nhắn đang chờ được thêm vào ngăn xếp cuộc gọi. Vòng lặp sự kiện là một quá trình vĩnh viễn kiểm tra xem ngăn xếp cuộc gọi có trống không. Nếu đúng như vậy, thì mục đầu tiên trong hàng đợi tin nhắn sẽ được chuyển đến ngăn xếp cuộc gọi. JavaScript ưu tiên các hàm trong hàng đợi tin nhắn hơn là các lệnh gọi hàm mà nó diễn giải trong mã. Hiệu ứng kết hợp của ngăn xếp cuộc gọi, hàng đợi tin nhắn và vòng lặp sự kiện cho phép mã JavaScript được xử lý trong khi quản lý các hoạt động không đồng bộ.

Đến đây bạn đã có hiểu biết cấp cao về vòng lặp sự kiện, bạn biết mã không đồng bộ bạn viết sẽ được thực thi như thế nào. Với kiến thức này, bây giờ bạn có thể tạo mã không đồng bộ với ba cách tiếp cận khác nhau: callbacks, những lời hứa, và async / await .

Lập trình không đồng bộ với lệnh gọi lại

Một hàm gọi lại là một hàm được truyền dưới dạng đối số cho một hàm khác và sau đó được thực thi khi hàm khác kết thúc. Ta sử dụng các lệnh gọi lại đảm bảo rằng mã chỉ được thực thi sau khi hoàn thành một hoạt động không đồng bộ.

Trong một thời gian dài, các lệnh gọi lại là cơ chế phổ biến nhất để viết mã không đồng bộ, nhưng bây giờ chúng phần lớn đã trở nên lỗi thời vì chúng có thể làm cho mã khó đọc.Trong bước này, bạn sẽ viết một ví dụ về mã không đồng bộ bằng cách sử dụng lệnh gọi lại để bạn có thể sử dụng nó làm cơ sở để xem hiệu quả tăng lên của các chiến lược khác.

Có nhiều cách để sử dụng các hàm gọi lại trong một hàm khác. Nói chung, họ có cấu trúc này:

function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {     [ Action ] } 

Mặc dù JavaScript hoặc Node.js không yêu cầu về mặt cú pháp để có hàm gọi lại làm đối số cuối cùng của hàm bên ngoài, nhưng thực tế phổ biến là giúp xác định các lệnh gọi lại dễ dàng hơn. Các nhà phát triển JavaScript cũng thường sử dụng một hàm ẩn danh như một lệnh gọi lại. Các hàm ẩn danh là những hàm được tạo ra mà không có tên. Nó thường dễ đọc hơn nhiều khi một hàm được xác định ở cuối danh sách đối số.

Để chứng minh các lệnh gọi lại, hãy tạo một module Node.js ghi danh sách các phim của Studio Ghibli vào một file . Trước tiên, hãy tạo một folder sẽ lưu trữ file JavaScript của ta và kết quả của nó:

  • mkdir ghibliMovies

Sau đó nhập folder đó:

  • cd ghibliMovies

Ta sẽ bắt đầu bằng cách thực hiện một yêu cầu HTTP tới API Studio Ghibli , mà hàm gọi lại của ta sẽ ghi lại kết quả. Để thực hiện việc này, ta sẽ cài đặt một thư viện cho phép ta truy cập dữ liệu của một phản hồi HTTP trong một cuộc gọi lại.

Trong terminal của bạn, hãy khởi tạo npm để ta có thể có tham chiếu cho các gói của bạn sau này:

  • npm init -y

Sau đó, cài đặt thư viện request :

  • npm i request --save

Bây giờ, hãy mở một file mới có tên callbackMovies.js trong một editor như nano :

  • nano callbackMovies.js

Trong editor của bạn, hãy nhập mã sau. Hãy bắt đầu bằng cách gửi một yêu cầu HTTP với module request :

callbackMovies.js
const request = require('request');  request('https://ghibliapi.herokuapp.com/films'); 

Trong dòng đầu tiên, ta tải module request đã được cài đặt qua npm. Mô-đun trả về một hàm có thể thực hiện các yêu cầu HTTP; sau đó ta lưu hàm đó trong hằng số request .

Sau đó, ta thực hiện yêu cầu HTTP bằng cách sử dụng hàm request() . Bây giờ hãy in dữ liệu từ yêu cầu HTTP vào console bằng cách thêm các thay đổi được đánh dấu:

callbackMovies.js
const request = require('request');  request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {     if (error) {         console.error(`Could not send request to API: ${error.message}`);         return;     }      if (response.statusCode != 200) {         console.error(`Expected status code 200 but received ${response.statusCode}.`);         return;     }      console.log('Processing our list of movies');     movies = JSON.parse(body);     movies.forEach(movie => {         console.log(`${movie['title']}, ${movie['release_date']}`);     }); }); 

Khi ta sử dụng hàm request() , ta cung cấp cho nó hai tham số:

  • URL của trang web ta đang cố gắng yêu cầu
  • Chức năng gọi lại xử lý mọi lỗi hoặc phản hồi thành công sau khi yêu cầu hoàn tất

Hàm callback ta có ba đối số: error , response , và body . Khi yêu cầu HTTP hoàn tất, các đối số sẽ tự động được cung cấp các giá trị tùy thuộc vào kết quả. Nếu yêu cầu không gửi, sau đó error sẽ chứa một đối tượng, nhưng responsebody sẽ null . Nếu nó thực hiện yêu cầu thành công, thì phản hồi HTTP được lưu trữ trong response . Nếu lợi nhuận của ta đáp ứng HTTP dữ liệu (trong ví dụ này, ta có được JSON) thì dữ liệu được đặt trong body .

Trước tiên, chức năng gọi lại của ta sẽ kiểm tra xem ta có gặp lỗi hay không. Cách tốt nhất là kiểm tra lỗi trong lệnh gọi lại trước để việc thực hiện lệnh gọi lại sẽ không tiếp tục với dữ liệu bị thiếu. Trong trường hợp này, ta ghi lại lỗi và việc thực thi chức năng. Sau đó, ta kiểm tra mã trạng thái của phản hồi. Server của ta có thể không phải lúc nào cũng khả dụng và các API có thể thay đổi khiến một khi các yêu cầu hợp lý trở nên không chính xác. Bằng cách kiểm tra rằng mã trạng thái là 200 , nghĩa là yêu cầu là "OK", ta có thể tin tưởng rằng phản hồi của ta là những gì ta mong đợi.

Cuối cùng, ta phân tích cú pháp phần thân phản hồi thành một Array và lặp qua từng bộ phim để ghi lại tên và năm phát hành của nó.

Sau khi lưu và thoát khỏi file , hãy chạy tập lệnh này với:

  • node callbackMovies.js

Bạn sẽ nhận được kết quả sau:

Output
Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014

Ta đã nhận được thành công danh sách các phim của Studio Ghibli với năm phát hành. Bây giờ ta hãy hoàn thành chương trình này bằng cách viết danh sách phim mà ta đang đăng nhập vào một file .

Cập nhật file callbackMovies.js trong editor của bạn để bao gồm mã được đánh dấu sau, mã này sẽ tạo file CSV với dữ liệu phim của ta :

callbackMovies.js
const request = require('request'); const fs = require('fs');  request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {     if (error) {         console.error(`Could not send request to API: ${error.message}`);         return;     }      if (response.statusCode != 200) {         console.error(`Expected status code 200 but received ${response.statusCode}.`);         return;     }      console.log('Processing our list of movies');     movies = JSON.parse(body);     let movieList = '';     movies.forEach(movie => {         movieList += `${movie['title']}, ${movie['release_date']}\n`;     });      fs.writeFile('callbackMovies.csv', movieList, (error) => {         if (error) {             console.error(`Could not save the Ghibli movies to a file: ${error}`);             return;         }          console.log('Saved our list of movies to callbackMovies.csv');;     }); }); 

Lưu ý những thay đổi được đánh dấu, ta thấy rằng ta nhập module fs . Mô-đun này là tiêu chuẩn trong tất cả các cài đặt Node.js và nó chứa phương thức writeFile() có thể ghi không đồng bộ vào file .

Thay vì ghi dữ liệu vào console , bây giờ ta thêm nó vào một movieList biến chuỗi. Sau đó, ta sử dụng writeFile() để lưu nội dung của movieList vào một file mới— callbackMovies.csv . Cuối cùng, ta cung cấp một lệnh gọi lại cho hàm writeFile() , hàm này có một đối số: error . Điều này cho phép ta xử lý các trường hợp mà ta không thể ghi vào file , chẳng hạn như khi user mà ta đang chạy quy trình node không có các quyền đó.

Lưu file và chạy lại chương trình Node.js này với:

  • node callbackMovies.js

Trong folder ghibliMovies của bạn, bạn sẽ thấy callbackMovies.csv , có nội dung sau:

callbackMovies.csv
Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 

Điều quan trọng cần lưu ý là ta ghi vào file CSV của bạn trong lệnh gọi lại của yêu cầu HTTP. Khi mã ở trong hàm gọi lại, nó sẽ chỉ ghi vào file sau khi yêu cầu HTTP được hoàn thành. Nếu ta muốn giao tiếp với database sau khi viết file CSV, ta sẽ tạo một hàm không đồng bộ khác sẽ được gọi trong lệnh gọi lại của writeFile() . Ta càng có nhiều mã không đồng bộ, thì càng có nhiều hàm gọi lại phải được lồng vào nhau.

Hãy tưởng tượng rằng ta muốn thực hiện năm hoạt động không đồng bộ, mỗi hoạt động chỉ có thể chạy khi một hoạt động khác hoàn thành. Nếu ta viết mã này, ta sẽ có thông tin như thế này:

doSomething1(() => {     doSomething2(() => {         doSomething3(() => {             doSomething4(() => {                 doSomething5(() => {                     // final action                 });             });         });      }); }); 

Khi các lệnh gọi lại lồng nhau có nhiều dòng mã để thực thi, chúng trở nên phức tạp hơn và khó đọc hơn đáng kể. Khi dự án JavaScript của bạn phát triển về quy mô và độ phức tạp, hiệu ứng này sẽ trở nên rõ rệt hơn, cho đến khi cuối cùng không thể quản lý được. Do đó, các nhà phát triển không còn sử dụng các lệnh gọi lại để xử lý các hoạt động không đồng bộ. Để cải thiện cú pháp của mã không đồng bộ, ta có thể sử dụng các lời hứa thay thế.

Sử dụng lời hứa để lập trình không đồng bộ ngắn gọn

Một lời hứa là một đối tượng JavaScript sẽ trả về một giá trị vào một thời điểm nào đó trong tương lai. Các hàm không đồng bộ có thể trả về các đối tượng hứa thay vì các giá trị cụ thể. Nếu ta nhận được một giá trị trong tương lai, ta nói rằng lời hứa đã được thực hiện. Nếu ta gặp lỗi trong tương lai, ta nói rằng lời hứa đã bị từ chối. Nếu không, lời hứa vẫn đang được thực hiện ở trạng thái đang chờ xử lý.

Lời hứa thường có dạng sau:

promiseFunction()     .then([ Callback Function for Fulfilled Promise ])     .catch([ Callback Function for Rejected Promise ]) 

Như trong mẫu này, các hứa hẹn cũng sử dụng các hàm gọi lại. Ta có một hàm gọi lại cho phương thức then() , được thực thi khi một lời hứa được hoàn thành. Ta cũng có một hàm gọi lại cho phương thức catch() để xử lý bất kỳ lỗi nào xuất hiện trong khi lời hứa đang được thực thi.

Hãy tận mắt trải nghiệm những lời hứa bằng cách viết lại chương trình Studio Ghibli của ta để sử dụng những lời hứa thay thế.

Axios là một ứng dụng HTTP dựa trên hứa hẹn cho JavaScript, vì vậy hãy tiếp tục và cài đặt nó:

  • npm i axios --save

Bây giờ, với editor mà bạn chọn, hãy tạo một file mới promiseMovies.js :

  • nano promiseMovies.js

Chương trình của ta sẽ thực hiện một yêu cầu HTTP với axios và sau đó sử dụng version fs đặc biệt được hứa hẹn dựa trên để lưu vào file CSV mới.

promiseMovies.js mã này vào promiseMovies.js để ta có thể tải Axios và gửi một yêu cầu HTTP đến API phim:

PromiseMovies.js
const axios = require('axios');  axios.get('https://ghibliapi.herokuapp.com/films'); 

Trong dòng đầu tiên, ta tải module axios , lưu trữ hàm trả về trong một hằng số được gọi là axios . Sau đó, ta sử dụng phương thức axios.get() để gửi một yêu cầu HTTP đến API.

Phương thức axios.get() trả về một lời hứa. Hãy xâu chuỗi lời hứa đó để ta có thể in danh sách các phim của Ghibli lên console :

PromiseMovies.js
const axios = require('axios'); const fs = require('fs').promises;   axios.get('https://ghibliapi.herokuapp.com/films')     .then((response) => {         console.log('Successfully retrieved our list of movies');         response.data.forEach(movie => {             console.log(`${movie['title']}, ${movie['release_date']}`);         });     }) 

Hãy chia nhỏ những gì đang xảy ra. Sau khi thực hiện một yêu cầu HTTP GET với axios.get() , ta sử dụng hàm then() , hàm này chỉ được thực thi khi lời hứa được thực hiện. Trong trường hợp này, ta in phim ra màn hình giống như ta đã làm trong ví dụ gọi lại.

Để cải thiện chương trình này, hãy thêm mã được đánh dấu để ghi dữ liệu HTTP vào file :

PromiseMovies.js
const axios = require('axios'); const fs = require('fs').promises;   axios.get('https://ghibliapi.herokuapp.com/films')     .then((response) => {         console.log('Successfully retrieved our list of movies');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}\n`;         });          return fs.writeFile('promiseMovies.csv', movieList);     })     .then(() => {         console.log('Saved our list of movies to promiseMovies.csv');     }) 

Ta cũng nhập lại module fs . Lưu ý cách sau khi nhập fs ta có .promises . Node.js bao gồm một version dựa trên hứa hẹn của thư viện fs dựa trên callback, vì vậy khả năng tương thích ngược không bị hỏng trong các dự án kế thừa.

Hàm then() đầu tiên xử lý yêu cầu HTTP bây giờ gọi fs.writeFile() thay vì in ra console . Vì ta đã nhập version dựa trên lời hứa của fs , writeFile() của ta trả về một lời hứa khác. Như vậy, ta thêm một then() chức năng cho khi writeFile() lời hứa được thực hiện.

Một lời hứa có thể trả về một lời hứa mới, cho phép ta thực hiện lần lượt các lời hứa. Điều này mở đường cho ta thực hiện nhiều hoạt động không đồng bộ. Đây được gọi là chuỗi lời hứa , và nó tương tự như lồng các lệnh gọi lại. Sau then() thứ hai chỉ được gọi sau khi ta ghi thành công vào file .

Lưu ý: Trong ví dụ này, ta đã không kiểm tra mã trạng thái HTTP như ta đã làm trong ví dụ gọi lại. Theo mặc định, axios không thực hiện lời hứa của nó nếu nó nhận được mã trạng thái chỉ ra lỗi. Như vậy, ta không cần xác thực nó nữa.

Để hoàn thành chương trình này, hãy xâu chuỗi lời hứa với một hàm catch() vì nó được đánh dấu trong phần sau:

PromiseMovies.js
const axios = require('axios'); const fs = require('fs').promises;   axios.get('https://ghibliapi.herokuapp.com/films')     .then((response) => {         console.log('Successfully retrieved our list of movies');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}\n`;         });          return fs.writeFile('promiseMovies.csv', movieList);     })     .then(() => {         console.log('Saved our list of movies to promiseMovies.csv');     })     .catch((error) => {         console.error(`Could not save the Ghibli movies to a file: ${error}`);     }); 

Nếu bất kỳ lời hứa nào không được thực hiện trong chuỗi lời hứa, JavaScript sẽ tự động chuyển đến hàm catch() nếu nó đã được định nghĩa. Đó là lý do tại sao ta chỉ có một mệnh đề catch() mặc dù ta có hai hoạt động không đồng bộ.

Hãy xác nhận chương trình của ta tạo ra cùng một kết quả bằng lệnh:

  • node promiseMovies.js

Trong folder ghibliMovies của bạn, bạn sẽ thấy file promiseMovies.csv chứa:

hứa hẹnMovies.csv
Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 

Với các lời hứa, ta có thể viết mã ngắn gọn hơn nhiều so với việc chỉ sử dụng các lệnh gọi lại. Chuỗi gọi lại hứa hẹn là một lựa chọn rõ ràng hơn là lồng các lệnh gọi lại. Tuy nhiên, khi ta thực hiện nhiều cuộc gọi không đồng bộ hơn, chuỗi hứa hẹn của ta sẽ trở nên dài hơn và khó duy trì hơn.

Tính chi tiết của các lệnh gọi lại và lời hứa xuất phát từ nhu cầu tạo các hàm khi ta có kết quả của một tác vụ không đồng bộ. Trải nghiệm tốt hơn sẽ là đợi kết quả không đồng bộ và đặt nó vào một biến bên ngoài hàm. Bằng cách đó, ta có thể sử dụng kết quả trong các biến mà không cần phải tạo một hàm. Ta có thể đạt được điều này với từ khóa asyncawait .

Viết JavaScript với async / await

Các từ khóa async / await cung cấp một cú pháp thay thế khi làm việc với các hứa hẹn. Thay vì có sẵn kết quả của một lời hứa trong phương thức then() , kết quả được trả về dưới dạng giá trị như trong bất kỳ hàm nào khác. Ta xác định một hàm với từ khóa async để cho JavaScript biết rằng đó là một hàm không đồng bộ trả về một lời hứa. Ta sử dụng từ khóa await để yêu cầu JavaScript trả về kết quả của lời hứa thay vì trả lại chính lời hứa khi nó được thực hiện.

Nói chung, cách sử dụng async / await trông giống như sau:

async function() {     await [Asynchronous Action] } 

Hãy xem cách sử dụng async / await có thể cải thiện chương trình Studio Ghibli của ta như thế nào. Sử dụng editor văn bản của bạn để tạo và mở file mới asyncAwaitMovies.js :

  • nano asyncAwaitMovies.js

Trong file JavaScript mới mở của bạn, hãy bắt đầu bằng lệnh các module tương tự mà ta đã sử dụng trong ví dụ hứa hẹn của ta :

asyncAwaitMovies.js
const axios = require('axios'); const fs = require('fs').promises; 

Các nhập giống như promiseMovies.jsasync / await sử dụng các hứa hẹn.

Bây giờ ta sử dụng từ khóa async để tạo một hàm với mã không đồng bộ của ta :

asyncAwaitMovies.js
const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {} 

Ta tạo ra một chức năng mới được gọi là saveMovies() nhưng ta bao gồm async vào đầu định nghĩa của nó. Điều này quan trọng vì ta chỉ có thể sử dụng từ khóa await trong một hàm không đồng bộ.

Sử dụng từ khóa await để thực hiện một yêu cầu HTTP nhận danh sách phim từ API Ghibli:

asyncAwaitMovies.js
const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     let response = await axios.get('https://ghibliapi.herokuapp.com/films');     let movieList = '';     response.data.forEach(movie => {         movieList += `${movie['title']}, ${movie['release_date']}\n`;     }); } 

Trong saveMovies() ta , ta thực hiện một yêu cầu HTTP với axios.get() như trước đây. Lần này, ta không chuỗi nó với một hàm then() . Thay vào đó, ta thêm await trước khi nó được gọi. Khi JavaScript thấy await , nó sẽ chỉ thực thi đoạn mã còn lại của hàm sau khi axios.get() kết thúc quá trình thực thi và đặt biến response . Đoạn mã còn lại lưu dữ liệu phim để ta có thể ghi vào file .

Hãy ghi dữ liệu phim vào một file :

asyncAwaitMovies.js
const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     let response = await axios.get('https://ghibliapi.herokuapp.com/films');     let movieList = '';     response.data.forEach(movie => {         movieList += `${movie['title']}, ${movie['release_date']}\n`;     });     await fs.writeFile('asyncAwaitMovies.csv', movieList); } 

Ta cũng sử dụng từ khóa await khi ta ghi vào file với fs.writeFile() .

Để hoàn thành chức năng này, ta cần bắt lỗi mà lời hứa của ta có thể mắc phải. Hãy làm điều này bằng cách đóng gói mã của ta trong một khối try / catch :

asyncAwaitMovies.js
const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     try {         let response = await axios.get('https://ghibliapi.herokuapp.com/films');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}\n`;         });         await fs.writeFile('asyncAwaitMovies.csv', movieList);     } catch (error) {         console.error(`Could not save the Ghibli movies to a file: ${error}`);     } }  

Vì các lời hứa có thể không thành công, ta mã hóa mã không đồng bộ của bạn bằng một mệnh đề try / catch . Điều này sẽ ghi lại bất kỳ lỗi nào được đưa ra khi yêu cầu HTTP hoặc hoạt động ghi file không thành công.

Cuối cùng, hãy gọi hàm không đồng bộ của ta là saveMovies() để nó sẽ được thực thi khi ta chạy chương trình với node

asyncAwaitMovies.js
const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     try {         let response = await axios.get('https://ghibliapi.herokuapp.com/films');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}\n`;         });         await fs.writeFile('asyncAwaitMovies.csv', movieList);     } catch (error) {         console.error(`Could not save the Ghibli movies to a file: ${error}`);     } }  saveMovies(); 

Thoạt nhìn, đây trông giống như một khối mã JavaScript đồng bộ điển hình. Nó có ít chức năng hơn được truyền xung quanh, trông gọn gàng hơn một chút. Những chỉnh sửa nhỏ này làm cho mã không đồng bộ với async / await dễ bảo trì hơn.

Kiểm tra sự lặp lại này của chương trình của ta bằng lệnh mã này vào terminal của bạn:

  • node asyncAwaitMovies.js

Trong folder ghibliMovies của bạn, một file asyncAwaitMovies.csv mới sẽ được tạo với nội dung sau:

asyncAwaitMovies.csv
Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 

Đến đây bạn đã sử dụng tính năng async / await của JavaScript để quản lý mã không đồng bộ.

Kết luận

Trong hướng dẫn này, bạn đã học cách JavaScript xử lý các hàm đang thực thi và quản lý các hoạt động không đồng bộ với vòng lặp sự kiện. Sau đó, bạn đã viết các chương trình tạo file CSV sau khi thực hiện yêu cầu HTTP cho dữ liệu phim bằng các kỹ thuật lập trình không đồng bộ khác nhau. Đầu tiên, bạn đã sử dụng cách tiếp cận dựa trên gọi lại lỗi thời. Sau đó, bạn sử dụng các lời hứa, và cuối cùng là async / await để làm cho cú pháp lời hứa ngắn gọn hơn.

Với sự hiểu biết của bạn về mã không đồng bộ với Node.js, giờ đây bạn có thể phát triển các chương trình hưởng lợi từ lập trình không đồng bộ, chẳng hạn như những chương trình dựa trên lệnh gọi API. Hãy xem danh sách các API công khai này . Để sử dụng chúng, bạn sẽ phải thực hiện các yêu cầu HTTP không đồng bộ như ta đã làm trong hướng dẫn này. Để nghiên cứu thêm, hãy thử xây dựng một ứng dụng sử dụng các API này để thực hành các kỹ thuật bạn đã học ở đây.


Tags:

Các tin liên quan