Як я можу використовувати async / чакати на найвищому рівні?


182

Я перебирав async/ awaitі переглянувши кілька статей, вирішив перевірити речі сам. Однак я, здається, не можу обернути голову, чому це не працює:

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

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

Консоль видає наступне (вузол v8.6.0):

> зовні: [об'єкт обіцяє]

> всередині: Ей там

Чому повідомлення журналу всередині функції виконується після цього? Я думав, що причина async/ awaitбула створена для того, щоб виконати синхронне виконання за допомогою асинхронних завдань.

Чи можу я використати значення, повернене всередині функції, не використовуючи .then()після main()?


4
Ні, лише машини часу можуть робити асинхронний код синхронним. awaitє не що інше, як цукор для thenсинтаксису обіцянок .
Бергі

Чому main повертає значення? Якщо так, мабуть, це не точка входу, і його потрібно викликати іншою функцією (наприклад, async IIFE).
колба Естуса

@estus це було лише швидке ім'я функції, коли я тестував речі у вузлі, не обов'язково представник програмиmain
Felipe

2
FYI, async/awaitє частиною ES2017, а не ES7 (ES2016)
Фелікс Клінг

Відповіді:


267

Я, здається, не можу обернути голову, чому це не працює.

Тому що mainповертає обіцянку; всі asyncфункції виконують.

На верхньому рівні ви повинні:

  1. Використовуйте функцію верхнього рівня, asyncяка ніколи не відхиляє (якщо ви не хочете помилок "без оброблення"), або

  2. Використовуйте thenі catch, або

  3. (Скоро!) Використовуйте пропозицію верхнього рівняawait , яка досягла 3 етапу в процесі, який дозволяє використовувати awaitмодуль верхнього рівня .

№1 - asyncфункція верхнього рівня, яка ніколи не відхиляє

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

Зауважте catch; ви повинні обробляти обіцянку відбраковування / асинхронне виняток, так як нічого іншого будуть; у вас немає абонента, щоб передати їх. Якщо ви віддаєте перевагу, ви могли б зробити це в результаті виклику його через catchфункцію (а не try/ catchсинтаксис):

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

... що трохи більш стисло (мені це подобається з цієї причини).

Або, звичайно, не обробляйте помилки, а просто дозвольте помилку "без оброблення".

№2 - thenіcatch

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

catchОброблювач буде викликатися при виникненні помилок в ланцюзі або в thenобробнику. (Будьте впевнені, що ваш catchобробник не видає помилок, оскільки для їх обробки не зареєстровано нічого.)

Або обидва аргументи then:

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

Знову зауважте, що ми реєструємо обробник відхилень. Але в цій формі будьте впевнені, що жоден з ваших thenзворотних викликів не призводить до помилок, нічого не зареєстровано для їх обробки.

№3 верхнього рівня awaitв модулі

Ви не можете використовувати awaitна верхньому рівні скрипта без модуля, але на вищому рівні awaitпропозиція ( етап 3 ) дозволяє використовувати його на верхньому рівні модуля. Це подібно до використання asyncобгортки функції верхнього рівня (№1 вище), оскільки ви не хочете, щоб ваш код верхнього рівня був відхилений (виникла помилка), оскільки це призведе до непоправної помилки відхилення. Тож якщо ви не хочете мати таке необроблене відхилення, коли справи йдуть не так, як це стосується №1, ви хочете загорнути свій код у оброблювач помилок:

// 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
}

Зауважте, що якщо ви це зробите, будь-який модуль, який імпортує з вашого модуля, буде чекати, поки обіцянки, які ви робите, не awaitзникнуть; коли модуль, що використовує верхній рівень await, оцінюється, він, в основному, повертає обіцянку завантажувачу модулів (як це asyncробить функція), який чекає, поки ця обіцянка буде вирішена, перш ніж оцінити тіла будь-яких модулів, які залежать від неї.


Розглядаючи це як обіцянку, тепер пояснюється, чому функція повертається негайно. Я експериментував із створенням функції анонімного асинхронного вищого рівня, і я отримую результати, які мають сенс зараз
Felipe

2
@Felipe: Так, async/ awaitсинтаксичний цукор навколо обіцянок (хороший сорт цукру :-)). Ви не просто думаєте про це як повернення обіцянки; це насправді так і є. ( Детальніше .)
TJ Crowder

1
@LukeMcGregor - я показав і те, і інше вище, перш за все asyncваріант. Для функції верхнього рівня я бачу це в будь-якому випадку (в основному через два рівні відступи на asyncверсії).
TJ Crowder

3
@Felipe - Я оновив відповідь зараз, коли пропозиція про вищий рівень awaitдосягла 3-го етапу. :-)
TJ Crowder

1
@SurajShrestha - Ні. Але це не проблема, що це не так. :-)
TJ Crowder

7

Топ-рівеньawait перейшов на 3-й етап, тож відповідь на ваше запитання Як я можу використовувати асинхронізацію / очікувати на найвищому рівні? це просто додати awaitдзвінок до 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)

Або просто:

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

Майте на увазі, що вона все ще доступна лише в Webpack@v5.0.0-alpha.15 .

Якщо ви використовуєте TypeScript , він приземлився в 3.8 .

v8 додала підтримку в модулях.

Його також підтримує Дено (як коментує gonzalo-bahamondez).


Дуже здорово. Чи є у нас дорожня карта щодо реалізації вузла
Феліпе

Не знаю, але, швидше за все, ми скоро побачимо реалізацію TypeScript і Babel. Команда TypeScript має політику впровадження мовних функцій 3-го ступеня, а плагін Babel зазвичай будується як частина процесу TC39 для тестування пропозицій. Дивіться сторінку github.com/Microsoft/TypeScript/isissue/…
Таро

Вона доступна в Діно теж (тільки JS, машинопис ще не підтримує github.com/microsoft/TypeScript/issues/25988 ) deno.land см deno.news/issues / ... .
Гонсало

SyntaxError: очікування дійсне лише у функції асинхронізації
Sudipta Dhara

4

Актуальним рішенням цієї проблеми є підхід до неї по-різному.

Можливо, ваша мета - це якась ініціалізація, яка зазвичай відбувається на верхньому рівні програми.

Рішення полягає в тому, щоб переконатися, що на найвищому рівні вашої програми завжди залишається одне єдине твердження JavaScript. Якщо у вас є лише одне твердження у верхній частині вашої програми, ви можете вільно використовувати async / очікувати в будь-якій іншій точці будь-де (будь-коли, за умови нормальних синтаксичних правил)

По-іншому, загортайте весь верхній рівень у функцію, щоб вона більше не була верхньою, і це вирішує питання про те, як запустити асинхронізацію / очікувати на верхньому рівні програми - ви цього не зробите.

Ось як має виглядати верхній рівень вашої програми:

import {application} from './server'

application();

1
Ви вірні, що моя мета - ініціалізація. Такі речі, як підключення до бази даних, отримання даних тощо. У деяких випадках необхідно було отримати дані користувача, перш ніж продовжувати роботу з додатком. По суті, ви пропонуєте це application()бути асинхронним?
Феліпе

1
Ні, я просто кажу, що якщо в корені вашої програми є лише один оператор JavaScript, то ваша проблема відсутня - заява верхнього рівня, як показано, не є асинхронною. Проблема полягає в тому, що неможливо використовувати асинхронізацію на верхньому рівні - ви не можете чекати, коли насправді очікують на цьому рівні - тому, якщо на найвищому рівні є колись одна заява, то ви усунули цю проблему. Ваш ініціалізаційний код асинхронізації тепер введений у деякий імпортований код, тому асинхронізація буде працювати чудово, і ви можете ініціалізувати все на початку вашої програми.
Герцог Дугал

1
КОРЕКЦІЯ - додаток є функцією асинхронізації.
Герцог Дугал

4
Мені не шкода. Справа в тому, що зазвичай на верхньому рівні функція асинхронізації не чекає .... JavaScript переходить безпосередньо до наступного твердження, тому ви не можете бути впевнені, що ваш код init завершений. Якщо у верхній частині вашої програми є лише одне твердження, це просто не має значення.
Герцог Дугал

3

Щоб отримати додаткову інформацію поверх поточних відповідей:

Вміст node.jsфайлу в даний час з'єднаний в рядковий спосіб, щоб утворити тіло функції.

Наприклад, якщо у вас є файл test.js:

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

Тоді node.jsбуде таємно об'єднати функцію, яка виглядає так:

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

Головне, що слід зазначити, це те, що отримана функція НЕ є функцією асинхронізації. Таким чином, ви не можете використовувати термін awaitбезпосередньо всередині нього!

Але скажіть, що вам потрібно працювати з обіцянками в цьому файлі, тоді є два можливі методи:

  1. Не використовуйте await безпосередньо всередині функції
  2. Не використовуйте await

Варіант 1 вимагає, щоб ми створили новий діапазон (і ЦЕ область може бути async, оскільки ми маємо над ним контроль):

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

Варіант 2 вимагає використання об'єктно-орієнтованого API обіцянки (менш симпатична, але однаково функціональна парадигма роботи з обіцянками)

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

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

Я особисто сподіваюся, що, якщо це працює, node.js за замовчуванням об'єднає код у asyncфункцію. Це дозволило б позбутися від цього головного болю.


0

Очікування верхнього рівня - це особливість майбутнього стандарту EcmaScript. В даний час ви можете почати використовувати його з TypeScript 3.8 (на даний момент у версії RC).

Як встановити TypeScript 3.8

Ви можете почати використовувати TypeScript 3.8, встановивши його з npm за допомогою наступної команди:

$ npm install typescript@rc

На цей час вам потрібно додати rcтег для встановлення останньої версії 3.8.


Але вам потрібно буде пояснити, як його потім використовувати?
raarts

-2

Оскільки main()працює асинхронно, він повертає обіцянку. Ви повинні отримати результат then()методом. А оскільки then()повернення обіцяють теж, вам доведеться зателефонувати, process.exit()щоб закінчити програму.

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

2
Неправильно. Після того, як усі обіцянки були прийняті або відхилені і більше не працює код у головній нитці, процес припиняється сам собою.

@Dev: зазвичай ви хочете передати різні значення, exit()щоб сигналізувати, чи сталася помилка.
9000

@ 9000 Так, але це не робиться тут, і оскільки код виходу 0 є типовим, не потрібно його включати

@ 9000 насправді, обробник помилок, ймовірно, повинен використовуватиprocess.exit(1)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.