typescript - async/await trong Angular `ngOnInit`
Hiện tại tôi đang đánh giá những lợi ích và bất lợi của việc thay thế Observable tương ứng của Angular và RxJS bằng Promise thuần túy để tôi có thể sử dụng async và await và có được một phong cách mã trực quan hơn.
Một trong những kịch bản điển hình của chúng tôi: Tải một số dữ liệu trong ngOnInit. Sử dụng Observables, chúng tôi làm như sau:
ngOnInit () {
this.service.getData().subscribe(data => {
this.data = this.modifyMyData(data);
});
}
Khi tôi trả về một Promise từ getData() thay vào đó, và sử dụng async và await, nó sẽ trở thành:
async ngOnInit () {
const data = await this.service.getData();
this.data = this.modifyMyData(data);
}
Bây giờ, rõ ràng là Angular sẽ không ‘biết’ rằng ngOnInit đã trở thành async. Tôi cảm thấy điều này không phải là vấn đề: Ứng dụng của tôi vẫn hoạt động như trước. Nhưng khi tôi nhìn vào giao diện OnInit, hàm này rõ ràng không được khai báo theo cách gợi ý rằng nó có thể được khai báo là async:
ngOnInit(): void;
Vậy -- tóm lại: Liệu những gì tôi đang làm ở đây có hợp lý không? Hay tôi sẽ gặp phải những vấn đề không lường trước được?
Câu trả lời #1
Nó không khác gì so với những gì bạn đã có trước đây. ngOnInit sẽ trả về một Promise và người gọi sẽ bỏ qua promise đó. Điều này có nghĩa là người gọi sẽ không chờ mọi thứ trong phương thức của bạn hoàn tất trước khi tiếp tục. Trong trường hợp cụ thể này, nó có nghĩa là giao diện sẽ được cấu hình xong và giao diện có thể được khởi chạy trước khi this.data được thiết lập.
Đó là tình huống giống như bạn đã gặp trước đây. Người gọi sẽ không chờ các đăng ký của bạn hoàn tất và có thể khởi chạy ứng dụng trước khi this.data được điền đầy đủ. Nếu view của bạn dựa vào data thì có khả năng bạn đã thiết lập một số loại ngIf để ngăn bạn truy cập vào nó.
Cá nhân tôi không thấy điều đó là lúng túng hay thực hành xấu miễn là bạn nhận thức được những hệ quả. Tuy nhiên, ngIf có thể trở nên phiền phức (chúng sẽ cần thiết trong mọi trường hợp). Cá nhân tôi đã chuyển sang sử dụng route resolvers khi hợp lý để tránh tình huống này. Dữ liệu được tải trước khi route hoàn tất điều hướng và tôi có thể biết dữ liệu đã sẵn có trước khi giao diện được tải.
Câu trả lời #2
Bây giờ, rõ ràng, Angular sẽ không 'biết' rằng ngOnInit đã trở thành async. Tôi cảm thấy đây không phải là vấn đề: Ứng dụng của tôi vẫn hoạt động như trước.
Về mặt ngữ nghĩa, nó sẽ biên dịch tốt và chạy như mong đợi, nhưng sự tiện lợi của việc viết async/wait đi kèm với chi phí về xử lý lỗi, và tôi nghĩ nên tránh.
Hãy xem điều gì xảy ra.
Điều gì xảy ra khi một lời hứa bị từ chối:
public ngOnInit() {
const p = new Promise((resolver, reject) => reject(-1));
}
Phần trên tạo ra ngăn xếp lỗi sau:
core.js:6014 ERROR Error: Uncaught (in promise): -1
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at new ZoneAwarePromise (zone-evergreen.js:876) [angular]
at ExampleComponent.ngOnInit (example.component.ts:44) [angular]
.....
Chúng ta có thể thấy rõ rằng lỗi không được xử lý đã được kích hoạt bởi một ngOnInit và cũng có thể thấy được tệp mã nguồn nào để tìm dòng mã gây lỗi.
Điều gì sẽ xảy ra khi chúng ta sử dụng async/wait mà bị từ chối:
public async ngOnInit() {
const p = await new Promise((resolver, reject) => reject());
}
Phần trên tạo ra ngăn xếp lỗi sau:
core.js:6014 ERROR Error: Uncaught (in promise):
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at rejected (tslib.es6.js:71) [angular]
at Object.onInvoke (core.js:39699) [angular]
at :4200/polyfills.js:4090:36 [angular]
at Object.onInvokeTask (core.js:39680) [angular]
at drainMicroTaskQueue (zone-evergreen.js:559) [<root>]
Chuyện gì đã xảy ra? Chúng tôi không biết, vì dấu vết ngăn xếp nằm ngoài thành phần.
Tuy nhiên, bạn có thể bị cám dỗ để sử dụng promise và chỉ tránh sử dụng async/wait. Vậy hãy xem điều gì sẽ xảy ra nếu một promise bị từ chối sau một setTimeout().
public ngOnInit() {
const p = new Promise((resolver, reject) => {
setTimeout(() => reject(), 1000);
});
}
Chúng ta sẽ nhận được ngăn xếp theo dõi sau:
core.js:6014 ERROR Error: Uncaught (in promise): [object Undefined]
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at :4200/app-module.js:21450:30 [angular]
at Object.onInvokeTask (core.js:39680) [angular]
at timer (zone-evergreen.js:2650) [<root>]
Một lần nữa, chúng tôi đã mất bối cảnh ở đây và không biết đi đâu để sửa lỗi.
Các đối tượng Observable cũng gặp phải các tác dụng phụ giống như khi xử lý lỗi, nhưng thường các thông báo lỗi có chất lượng tốt hơn. Nếu ai đó sử dụng throwError(new Error()) thì đối tượng Error sẽ chứa một ngăn xếp theo dõi (stack trace), và nếu bạn đang sử dụng HttpModule thì đối tượng Error thường là một đối tượng Http response thông báo cho bạn về yêu cầu.
Vì vậy, bài học đạo đức ở đây là: Hãy bắt các lỗi của bạn, sử dụng observables khi có thể và đừng sử dụng async ngOnInit(), vì nó sẽ quay lại ám bạn như một lỗi khó tìm và sửa.
Câu trả lời #3
Tôi tự hỏi những nhược điểm của một biểu thức hàm được gọi ngay lập tức là gì :
ngOnInit () {
(async () => {
const data = await this.service.getData();
this.data = this.modifyMyData(data);
})();
}
Đây là cách duy nhất tôi có thể tưởng tượng để làm cho nó hoạt động mà không cần khai báo ngOnInit() như một hàm async
