Làm cách nào tôi có thể sử dụng async / await ở cấp cao nhất?


181

Tôi đã đi qua async/ awaitvà sau khi xem qua một vài bài báo, tôi quyết định tự mình kiểm tra mọi thứ. Tuy nhiên, tôi dường như không thể quấn đầu xung quanh tại sao điều này không hoạt động:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

Bảng điều khiển xuất ra các mục sau (nút v8.6.0):

> bên ngoài: [đối tượng Promise]

> bên trong: Này đó

Tại sao thông điệp tường trình bên trong hàm thực thi sau đó? Tôi nghĩ lý do async/ awaitđược tạo ra là để thực hiện thực thi đồng bộ bằng cách sử dụng các tác vụ không đồng bộ.

Có cách nào tôi có thể sử dụng giá trị được trả về bên trong hàm mà không sử dụng .then()sau main()không?


4
Không, chỉ có máy thời gian mới có thể tạo mã không đồng bộ. awaitkhông có gì ngoài đường cho thencú pháp lời hứa .
Bergi

Tại sao chính trả về một giá trị? Nếu cần, có lẽ đó không phải là điểm vào và cần được gọi bởi một chức năng khác (ví dụ async IIFE).
Bình Estus

@estus nó chỉ là một tên hàm nhanh trong khi tôi đang thử nghiệm mọi thứ trong nút, không nhất thiết phải đại diện cho một chương trìnhmain
Felipe

2
FYI, async/awaitlà một phần của ES2017, không phải ES7 (ES2016)
Felix Kling

Câu trả lời:


266

Tôi dường như không thể quấn đầu xung quanh tại sao điều này không hoạt động.

Bởi vì maintrả lại một lời hứa; tất cả các asyncchức năng làm.

Ở cấp cao nhất, bạn phải:

  1. Sử dụng asyncchức năng cấp cao nhất không bao giờ từ chối (trừ khi bạn muốn các lỗi "từ chối chưa xử lý") hoặc

  2. Sử dụng thencatch, hoặc

  3. (Sắp có!) Sử dụng cấp cao nhấtawait , một đề xuất đã đạt đến Giai đoạn 3 trong quy trình cho phép sử dụng cấp cao nhất awaittrong một mô-đun.

# 1 - asyncChức năng cấp cao nhất không bao giờ từ chối

(async () => {
    try {
        var text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
})();

Lưu ý catch; bạn phải xử lý các trường hợp ngoại lệ từ chối / không đồng bộ, vì không có gì khác xảy ra; bạn không có người gọi để chuyển chúng đến. Nếu bạn thích, bạn có thể làm điều đó với kết quả của việc gọi nó thông qua catchhàm (chứ không phải try/ catchcú pháp):

(async () => {
    var text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});

... Đó là một chút ngắn gọn hơn (tôi thích nó vì lý do đó).

Hoặc, tất nhiên, không xử lý lỗi và chỉ cho phép lỗi "từ chối không xử lý".

# 2 - thencatch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });

Trình catchxử lý sẽ được gọi nếu xảy ra lỗi trong chuỗi hoặc trong thentrình xử lý của bạn . (Hãy chắc chắn rằng catchtrình xử lý của bạn không ném lỗi, vì không có gì được đăng ký để xử lý chúng.)

Hoặc cả hai đối số để then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);

Một lần nữa thông báo chúng tôi đang đăng ký một trình xử lý từ chối. Nhưng trong hình thức này, hãy chắc chắn rằng cả hai thencuộc gọi lại của bạn không gây ra bất kỳ lỗi nào, không có gì được đăng ký để xử lý chúng.

# 3 cấp cao nhất awaittrong một mô-đun

Bạn không thể sử dụng awaitở cấp cao nhất của tập lệnh không phải mô-đun, nhưng đề xuất cấp cao nhấtawait ( Giai đoạn 3 ) cho phép bạn sử dụng nó ở cấp cao nhất của mô-đun. Nó tương tự như sử dụng asynctrình bao bọc hàm cấp cao nhất (số 1 ở trên) ở chỗ bạn không muốn mã cấp cao nhất của mình từ chối (ném lỗi) vì điều đó sẽ dẫn đến lỗi từ chối không được xử lý. Vì vậy, trừ khi bạn muốn có sự từ chối chưa được xử lý đó khi có sự cố xảy ra, như với # 1, bạn muốn bọc mã của mình trong một trình xử lý lỗi:

// In a module, once the top-level `await` proposal lands
try {
    var text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}

Lưu ý rằng nếu bạn thực hiện việc này, mọi mô-đun nhập từ mô-đun của bạn sẽ đợi cho đến khi lời hứa bạn awaitgiải quyết; khi một mô-đun sử dụng mức cao nhất awaitđược đánh giá, về cơ bản nó sẽ trả lại một lời hứa cho trình tải mô-đun (giống như một asyncchức năng), chờ cho đến khi lời hứa đó được giải quyết trước khi đánh giá các phần của bất kỳ mô-đun nào phụ thuộc vào nó.


Nghĩ về nó như một lời hứa giải thích tại sao chức năng trở lại ngay lập tức. Tôi đã thử nghiệm tạo một hàm async ẩn danh cấp cao nhất và tôi nhận được kết quả có ý nghĩa ngay bây giờ
Felipe

2
@Felipe: Có, async/ awaitlà đường cú pháp xung quanh những lời hứa (loại đường tốt :-)). Bạn không chỉ nghĩ về nó như trả lại một lời hứa; nó thực sự làm ( Chi tiết .)
TJ Crowder

1
@LukeMcGregor - Tôi đã hiển thị cả hai ở trên, với asynctùy chọn all- trước tiên. Đối với chức năng cấp cao nhất, tôi có thể thấy một trong hai cách (chủ yếu là do hai cấp độ thụt vào asyncphiên bản).
TJ Crowder

3
@Felipe - Tôi đã cập nhật câu trả lời ngay bây giờ rằng awaitđề xuất cấp cao nhất đã đạt đến Giai đoạn 3. :-)
TJ Crowder

1
@SurajShrestha - Không. Nhưng đó không phải là vấn đề mà nó không xảy ra. :-)
TJ Crowder

7

Cấp cao nhấtawait đã chuyển sang giai đoạn 3, vì vậy câu trả lời cho câu hỏi của bạn Làm cách nào tôi có thể sử dụng async / await ở cấp cao nhất? là chỉ cần thêm awaitcuộc gọi đến main():

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

Hoặc chỉ:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Hãy nhớ rằng nó vẫn chỉ có sẵn trong Webpack@v5.0.0-alpha.15 .

Nếu bạn đang sử dụng TypeScript , nó đã hạ cánh trong 3.8 .

v8 đã thêm hỗ trợ trong các mô-đun.

Nó cũng được Deno hỗ trợ (như nhận xét của gonzalo-bahamondez).


Tuyệt đấy. Chúng ta có lộ trình nào để thực hiện Nút không
Felipe

Không biết, nhưng rất có thể chúng ta sẽ sớm thấy triển khai TypeScript và Babel. Nhóm TypeScript có chính sách triển khai các tính năng ngôn ngữ giai đoạn 3 và plugin Babel thường được xây dựng như một phần của quy trình TC39 để kiểm tra các đề xuất. Xem github.com/Microsoft/TypeScript/issues/ Kẻ
Taro

Nó cũng có sẵn trong deno (chỉ js, typecript vẫn không hỗ trợ nó github.com/microsoft/TypeScript/issues/25988 ) deno.land xem deno.news/issues/ trộm .
Gonzalo Bahamondez

Cú pháp: chờ đợi chỉ có hiệu lực trong chức năng không đồng bộ
Sudipta Dhara

4

Giải pháp thực tế cho vấn đề này là tiếp cận nó một cách khác biệt.

Có lẽ mục tiêu của bạn là một số loại khởi tạo thường xảy ra ở cấp cao nhất của ứng dụng.

Giải pháp là đảm bảo rằng chỉ có một câu lệnh JavaScript duy nhất ở cấp cao nhất của ứng dụng của bạn. Nếu bạn chỉ có một câu lệnh ở đầu ứng dụng của mình, thì bạn có thể tự do sử dụng async / await ở mọi điểm khác ở mọi nơi (tất nhiên là theo quy tắc cú pháp thông thường)

Đặt một cách khác, bọc toàn bộ cấp cao nhất của bạn trong một chức năng để nó không còn là cấp cao nhất và giải quyết câu hỏi về cách chạy async / await ở cấp cao nhất của ứng dụng - bạn không.

Đây là cấp độ cao nhất của ứng dụng của bạn sẽ như thế nào:

import {application} from './server'

application();

1
Bạn đúng rằng mục tiêu của tôi là khởi tạo. Những thứ như kết nối cơ sở dữ liệu, dữ liệu kéo, v.v ... Trong một số trường hợp cần lấy dữ liệu của người dùng trước khi tiếp tục với phần còn lại của ứng dụng. Thực chất bạn đang đề xuất application()là không đồng bộ?
Felipe

1
Không, tôi chỉ nói rằng nếu chỉ có một câu lệnh JavaScript ở gốc ứng dụng thì vấn đề của bạn không còn nữa - câu lệnh cấp cao nhất như được hiển thị không đồng bộ. Vấn đề là không thể sử dụng async ở cấp cao nhất - bạn không thể chờ đợi để thực sự chờ đợi ở cấp đó - do đó, nếu chỉ có một tuyên bố ở cấp cao nhất thì bạn đã bỏ qua vấn đề đó. Mã async khởi tạo của bạn hiện không còn trong một số mã được nhập và do đó async sẽ hoạt động tốt và bạn có thể khởi tạo mọi thứ khi bắt đầu ứng dụng.
Công tước Dougal

1
ĐÚNG - ứng dụng là một chức năng không đồng bộ.
Công tước Dougal

4
Tôi không rõ ràng xin lỗi. Vấn đề là thông thường, ở cấp cao nhất, một hàm async không chờ đợi .... JavaScript đi thẳng vào câu lệnh tiếp theo để bạn không thể chắc chắn rằng mã init của bạn đã hoàn thành. Nếu chỉ có một tuyên bố duy nhất ở đầu ứng dụng của bạn thì điều đó không thành vấn đề.
Công tước Dougal

3

Để cung cấp thêm một số thông tin trên đầu câu trả lời hiện tại:

Nội dung của một node.jstệp hiện đang được nối, theo cách giống như chuỗi, để tạo thành một thân hàm.

Ví dụ: nếu bạn có một tệp test.js:

// Amazing test file!
console.log('Test!');

Sau đó node.jssẽ bí mật ghép một chức năng trông giống như:

function(require, __dirname, ... a bunch more top-level properties) {
  // Amazing test file!
  console.log('test!');
}

Điều quan trọng cần lưu ý là hàm kết quả KHÔNG phải là hàm không đồng bộ. Vì vậy, bạn không thể sử dụng thuật ngữ awaittrực tiếp bên trong nó!

Nhưng nói rằng bạn cần phải làm việc với các lời hứa trong tệp này, thì có hai phương pháp có thể:

  1. Không sử dụng await trực tiếp bên trong chức năng
  2. Đừng dùng await

Tùy chọn 1 yêu cầu chúng tôi tạo một phạm vi mới (và phạm vi NÀY có thể async, bởi vì chúng tôi có quyền kiểm soát nó):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

Tùy chọn 2 yêu cầu chúng tôi sử dụng API lời hứa hướng đối tượng (mô hình ít hoạt động nhưng không kém phần chức năng để làm việc với các lời hứa)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

Cá nhân tôi hy vọng rằng, nếu nó khả thi, thì node.js theo mặc định sẽ ghép mã thành một asynchàm. Điều đó sẽ thoát khỏi cơn đau đầu này.


0

Chờ đợi cấp cao nhất là một tính năng của tiêu chuẩn EcmaScript sắp tới. Hiện tại, bạn có thể bắt đầu sử dụng nó với TypeScript 3.8 (trong phiên bản RC tại thời điểm này).

Cách cài đặt TypeScript 3.8

Bạn có thể bắt đầu sử dụng TypeScript 3.8 bằng cách cài đặt nó từ npm bằng lệnh sau:

$ npm install typescript@rc

Tại thời điểm này, bạn cần thêm rcthẻ để cài đặt phiên bản bản mới nhất 3.8.


Nhưng bạn sẽ cần phải giải thích làm thế nào để sử dụng nó sau đó?
raart

-2

main()chạy không đồng bộ, nó trả lại một lời hứa. Bạn phải có được kết quả trong then()phương pháp. Và bởi vìthen() trả lại lời hứa quá, bạn phải gọi process.exit()để kết thúc chương trình.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )

2
Sai lầm. Khi tất cả các lời hứa đã được chấp nhận hoặc từ chối và không còn mã nào đang chạy trong luồng chính, quy trình sẽ tự chấm dứt.

@Dev: thông thường bạn muốn chuyển các giá trị khác nhau exit()để báo hiệu xem có lỗi xảy ra hay không.
9000

@ 9000 Có, nhưng điều đó không được thực hiện ở đây và vì mã thoát 0 là mặc định nên không cần đưa nó

@ 9000 trên thực tế, trình xử lý lỗi có thể nên được sử dụngprocess.exit(1)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.