Как я могу использовать async / await на верхнем уровне?

186

Я перебирал 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 Колба
@estus это было просто быстрое имя функции, когда я тестировал вещи в узле, не обязательно репрезентативные для программыmain
Фелипе
2
К вашему сведению, async/awaitявляется частью ES2017, а не ES7 (ES2016)
Феликс Клинг

Ответы:

271

Кажется, я не могу понять, почему это не работает.

Потому что 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делает функция), которое ждет, пока это обещание не будет выполнено, прежде чем оценивать тела любых модулей, которые зависят от него.

TJ Crowder
источник
Думая об этом как об обещании, теперь объясняется, почему функция возвращается немедленно. Я экспериментировал с созданием анонимной асинхронной функции верхнего уровня, и теперь я получаю результаты, которые имеют смысл
Фелипе
2
@ Филип: Да, async/ awaitявляются синтаксическим сахаром вокруг обещаний (хороший вид сахара :-)). Вы не просто думаете об этом как о возвращении обещания; это действительно так. ( Подробности .)
TJ Crowder
1
@LukeMcGregor - я показал оба выше, с asyncопцией all- first. Для функции верхнего уровня я могу видеть это в любом случае (в основном из-за двух уровней отступа в asyncверсии).
Ти Джей Краудер
3
@Felipe - я обновил ответ теперь, когда предложение верхнего уровня awaitдостигло стадии 3. :-)
TJ Crowder
1
@SurajShrestha - Нет. Но это не проблема, что это не так. :-)
TJ Crowder
7

Верхний уровеньawait перешел на этап 3, поэтому ответ на ваш вопрос Как я могу использовать async / await на верхнем уровне? это просто добавить 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 .

В версии 8 добавлена ​​поддержка модулей.

Это также поддерживается Дено (как прокомментировал Гонсало-Бахамондез).

Taro
источник
Довольно круто. Есть ли у нас какая-либо дорожная карта для реализации Node
Фелипе
Не знаю, но очень вероятно, что мы скоро увидим реализацию TypeScript и Babel. Команда TypeScript придерживается политики реализации языковых возможностей этапа 3, а плагин Babel обычно создается как часть процесса TC39 для тестирования предложений. См. Github.com/Microsoft/TypeScript/issues/…
Taro
Он также доступен в deno (просто js, машинопись еще не поддерживается github.com/microsoft/TypeScript/issues/25988 ) deno.land см. Deno.news/issues/… .
Гонсало Бахамондез,
Ошибка синтаксиса: ожидание допустимо только в асинхронной функции
Судипта Дхара
4

Фактическое решение этой проблемы заключается в том, чтобы подойти к ней по-другому.

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

Решение состоит в том, чтобы на верхнем уровне вашего приложения был только один единственный оператор JavaScript. Если у вас есть только один оператор в верхней части вашего приложения, тогда вы можете свободно использовать async / await в любой другой точке в любом месте (при условии, конечно, нормальных правил синтаксиса)

Другими словами, оберните весь верхний уровень в функцию так, чтобы он больше не был верхним, и это решает вопрос о том, как запустить async / await на верхнем уровне приложения, а вы - нет.

Вот как должен выглядеть верхний уровень вашего приложения:

import {application} from './server'

application();
Герцог Дугал
источник
1
Вы правы, что моя цель - инициализация. Такие вещи, как соединения с базой данных, извлечение данных и т. Д. В некоторых случаях было необходимо получить данные пользователя, прежде чем продолжить работу с остальной частью приложения. По сути вы предлагаете application()быть асинхронным?
Фелипе
1
Нет, я просто говорю, что если в корне вашего приложения есть только один оператор JavaScript, то ваша проблема исчезнет - показанный выше оператор верхнего уровня не является асинхронным. Проблема в том, что невозможно использовать асинхронный режим на верхнем уровне - вы не можете ждать, пока вы действительно ожидаете на этом уровне - поэтому, если на верхнем уровне есть только один оператор, то вы обошли эту проблему. Ваш асинхронный код инициализации теперь не доступен в некотором импортированном коде, и поэтому асинхронная работа будет работать нормально, и вы можете инициализировать все в начале вашего приложения.
Герцог Дугал
1
ИСПРАВЛЕНИЕ - приложение является асинхронной функцией.
Герцог Дугал
4
Мне не ясно, извини. Дело в том, что обычно на верхнем уровне асинхронная функция не ожидает ... JavaScript переходит непосредственно к следующему утверждению, поэтому вы не можете быть уверены, что ваш код инициализации завершен. Если в верхней части приложения есть только одно утверждение, то это не имеет значения.
Герцог Дугал
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.

Ахмед Бушефра
источник
Но вам нужно будет объяснить, как его использовать тогда?
начинается
-2

Поскольку main()работает асинхронно, он возвращает обещание. Вы должны получить результат в then()методе. И потому что then()обещание возврата тоже, вы должны позвонить, process.exit()чтобы закончить программу.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
источник
2
Неправильно. Как только все обещания будут приняты или отклонены, и в главном потоке больше не будет кода, процесс завершится сам собой.
@Dev: обычно вы хотите передать разные значения, exit()чтобы сообщить, произошла ли ошибка.
9000
@ 9000 Да, но здесь этого не делается, и так как код выхода 0 является значением по умолчанию, включать его нет необходимости
@ 9000, фактически обработчик ошибок, вероятно, должен использоватьprocess.exit(1)